/*
	Catharsis (v1.0p)
	Alpha release (p = in progress).

	An  Amiga  shared  library  to  modify  ugly  looking  fonts  by  attaching  a  second,  modified
	character-bitmap where some pixels may be added or will be overwritten by the newly created.

	Status/Copyright:	PUBLIC DOMAIN SOFTWARE
	Author:	Joerg van de Loo	email: Joergloo@aol.com		(<- case sensitive)

	By the way:
		This  is  an  experimental  attempt  written  by  J.v.d.Loo  to try to make the 'times' fonts
		useable since these are the default for WEB-browsers.

	I  hope  this  library is useable and programmers modify their programs to support this library -
	so that the users have the benefit of readable fonts.

	I  would  greatly  appreciate when someone will improve this library or take over the development
	since I'm a bit lazy.
*/

#include <exec/memory.h>
#include <exec/libraries.h>

#include <dos/dos.h>

#include <graphics/gfxbase.h>
#include <graphics/gfx.h>
#include <graphics/rastport.h>
#include <graphics/text.h>

#include <intuition/intuitionbase.h>

#include <clib/exec_protos.h>
#include <clib/intuition_protos.h>
#include <clib/graphics_protos.h>

#include <pragma/exec_lib.h>
#include <pragma/intuition_lib.h>
#include <pragma/graphics_lib.h>

#if !defined(__MAXON__) && !defined(__STORM__)
#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/graphics.h>
#else
#include <pragma/exec_lib.h>
#include <pragma/intuition_lib.h>
#include <pragma/graphics_lib.h>
#endif

#ifdef __GNUC__
 #define ASM
 #define REG(reg,arg) arg __asm(#reg)
#else
 #if !defined (__MAXON__) 
 #define ASM __asm
 #endif
 #define REG(reg,arg) register __##reg arg
#endif

#ifdef __MAXON__
 extern "C" void GetBaseReg( void);
 #define __saveds
 #define ASM
#endif

#if defined(__MAXON__) || defined(__STORM__)
  #define __inline
  #define __stdargs
#endif

extern struct GfxBase *GfxBase;
extern struct IntuitionBase *IntuitionBase;


struct SharedLib
{
	struct Node sl_LibNode;
	UBYTE sl_Flags;							// LIB Flags
	UBYTE sl_Pad0;							// Dummy
	UWORD sl_NegSize;						// LIB Negative size (function table)
	UWORD sl_PosSize;						// LIB Positive size (it's this table)
	UWORD sl_Version;						// LIB Version
	UWORD sl_Revision;						// LIB Revision
	char *sl_IdString;						// LIB Detail string
	ULONG sl_Sum;							// LIB Checksum
	UWORD sl_OpenCnt;						// LIB Open count
	UWORD sl_PatchCnt;						// - Internally used
	BPTR sl_SegList;						// LIB Segment pointer
	void *sl_OldFuncPtr;
	struct ExecBase *sl_SysBase;			// Pointers to SysBase
	struct GfxBase *sl_GfxBase;
	struct IntuitionBase *sl_IntuitionBase;
	struct TextFont *sl_TextFont[256];		// Remembered TextFont pointers
	struct TextFont *sl_CatharsisFont[256];	// Created TextFont pointers
};											// Size of table (positive size)


/* Type definitions */

struct CharInfo
{
	unsigned short offset;
	unsigned short size;
};

struct CharArray
{
	struct CharInfo CharIndex[256];		// Points to each character's CharInfo (see above)
};

/* This table is used for decoding */
struct	FontDescription
{
	struct RastPort	rport;		// For existing, encoded data of font
	struct BitMap	bmap;
	struct RastPort	destrp;		// For the newly, slightly changed font data
	struct BitMap	destbm;

	unsigned char *data;		// Font data information pointer
	unsigned short bytesperrow;	// Complete line with information for all chars

	unsigned short lowchar;		// e.g. 32
	unsigned short hichar;		// e.g. 255

	unsigned short charheight;	// Height of this char
	unsigned short charoffset;	// The offset in pixels to reach the information for this char in "data"
	unsigned short charwidth;	// The amount of information stored per line for this char (e.g. 9)
};

/*	-------------------------------------------------------------------------------------------------

	This  is  the  basic  function to modify a single character which takes a given character (namely
	its  bitmap)  and  creates  a second character which pixels do not be necessarily in the original
	character,  in addition, it doesn't copies pixels one to one into the second character (-bitmap).
	Only  pixels which are not present in the original character will be stored in the newly created,
	with  the  exception  of  pixels that have to be eleminated respectively exchanged by a different
	colour to make this character better readable.

	The  newly  created  charcater  should  be rendered into the RastPort with a different colour and
	draw-mode JAM1 (JAM1 = do not erase background)
*/

void ModifyCharacter( struct FontDescription *fd)
{
	register UWORD c0, c, cc, x, charX, y;	// Let the compiler choose amount of reg-vars

	x = 0;									// No pixel in horizontal direction of this char worked out

	while (x + 1 < fd->charwidth)			// As long some pixels left to do (at least the square should be readable)
	{
		y = 0;								// We're at the top of this character

		charX = fd->charoffset + x; 		// Pixel position (X) within source and destination rastport

		while (y < fd->charheight - 1)		// As long as the square can be read in
		{
			/* Read a square of 2*2 pixels into 'c' and
			   a rectangle of 3*2 pixels into 'cc'
			*/

			if (y == 0)						// Only when at top of char we have to read in square's first row pixels...>
			{
				c = (UWORD) ReadPixel( &fd->rport, charX, y);
				c = (c << 1);
				c |= ReadPixel( &fd->rport, charX + 1, y);
				c = (c << 1);
			}
			else							// >... else we can use the existing
			{
				c = ((c & 3) << 1);			// Since next row is already in 'c'
			}

			if (x + 2 < fd->charwidth)		// Can the rectangle be read in?
			{
				if (y == 0)					// If we have to read in this pixel...
				{
					cc = c;
					cc |= ReadPixel( &fd->rport, charX + 2, y);	// 3rd pixel!
					cc = (cc << 1);
				}
				else
				{
					cc = ((cc & 7) << 1);	// Otherwise take the rectangle's 2nd row as 1st
				}
			}

			// ------------------------------------

			c0 = ReadPixel( &fd->rport, charX, y + 1);
			c |= c0;
			cc |= c0;
			c = (c << 1);
			cc = (cc << 1);

			c0 = ReadPixel( &fd->rport, charX + 1, y + 1);
			c |= c0;

			if (x + 2 < fd->charwidth)	// Can the rectangle be read in?
			{
				cc |= c0;
				cc = (cc << 1);
				cc |= ReadPixel( &fd->rport, charX + 2, y + 1);	// 3rd pixel!
			}
			else
			{
				cc = 0;
			}

			/* 'c' contains a 2*2 square in binary format, e.g:
				15 = ##		8 + 4
					 ##		2 + 1 = 8 + 4 + 2 + 1

				07 = .#		0 + 4
					 ##		2 + 1 = 0 + 4 + 2 + 1

				11 = #.		8 + 0
					 ##		4 + 2 = 8 + 0 + 2 + 1

				13 = ##		8 + 4
					 .#		0 + 1 = 8 + 4 + 0 + 1

				'cc' contains a rectangle of 3*2
			*/

			if (y <= fd->charheight / 2)	// One pixel more than halve of character's height when scanning from top to down
			{
				if (fd->charheight > 15)	// Fonts higher than 15 pixels require rectangles of 4*3 and 3*4, which isn't supported,
				{							// where the 4*3's value 230 must be changed to 192 and 3*4's 3288 to 3272 (erasing).
					switch (c)				// Font is higher than 15 pixels and we applying pixels within an area from top to middle of char
					{
						case 9:
							WritePixel( &fd->destrp, charX, y + 1);
							break;

						case 6:
							WritePixel( &fd->destrp, charX, y);
							break;

						case 7:
							WritePixel( &fd->destrp, charX, y);
							break;

						case 11:
							WritePixel( &fd->destrp, charX + 1, y);
							break;

						case 13:
							WritePixel( &fd->destrp, charX, y + 1);
							break;

						case 14:
							WritePixel( &fd->destrp, charX + 1, y + 1);
							break;
					}
				}
				else
				{
					switch (c)				// Font is smaller 15 pixels in height, thus apply/exchange pixels
					{
						case 9:
							WritePixel( &fd->destrp, charX, y + 1);
							break;

						case 6:
							WritePixel( &fd->destrp, charX, y);
							break;

						default:
							switch (cc)
							{
								case 30:
									WritePixel( &fd->destrp, charX + 1, y + 1);		// Displace pixels
									break;

								case 51:
									WritePixel( &fd->destrp, charX + 1, y + 1);		// Displace pixels
									break;

								default:	// NOTE: Following modifiers can make the font look more ugly!
									switch (c)
									{
										case 7:
											WritePixel( &fd->destrp, charX, y);
											break;

										case 13:
											WritePixel( &fd->destrp, charX, y + 1);
											break;
									}
									break;
							}		// end switch 'cc'
							break;	// break default
					
					}	// end switch 'cą'
				} // end inner ELSE
			}
			else	// We are going to modify an area from the middle to the bottom of the char
			{
				if (fd->charheight > 15)	// See above
				{
					switch (c)
					{
						case 9:
							WritePixel( &fd->destrp, charX, y + 1);
							break;

						case 6:
							WritePixel( &fd->destrp, charX, y);
							break;

						case 7:
							WritePixel( &fd->destrp, charX, y);
							break;

						case 11:
							WritePixel( &fd->destrp, charX + 1, y);
							break;

						case 13:
							WritePixel( &fd->destrp, charX, y + 1);
							break;

						case 14:
							WritePixel( &fd->destrp, charX + 1, y + 1);
							break;
					}
				}
				else
				{
					switch (c)
					{
						case 9:
							WritePixel( &fd->destrp, charX, y + 1);
							break;

						case 6:
							WritePixel( &fd->destrp, charX, y);
							break;

						default:
							switch (cc)
							{
								case 30:
									WritePixel( &fd->destrp, charX + 1, y);
									break;

								case 51:
									WritePixel( &fd->destrp, charX + 1, y);
									break;

								default:
									switch (c)
									{
										case 7:
											WritePixel( &fd->destrp, charX, y);
											break;

										case 13:
											WritePixel( &fd->destrp, charX, y + 1);
											break;
									}
									break;
							}		// end switch 'cc'
							break;	// break default
					
					}	// end switch 'cą'
				}
			}

			y ++;	// Continue at next row
		}

		x ++;	// Continue at next horizontal pixel and row zero
	}

}

void ModifyCharacterA( REG(a0,struct FontDescription *fd))
{
	ModifyCharacter( fd);
}

/* ---------------------------------------------------------------------------------- */

struct TextFont *CreateCatharsisFont( struct TextFont *font)
{
	struct TextFont *ctsfont;	// New font to create
	struct CharArray *farray;	// Pointer to the characters specific data
	unsigned short textchar;	// A simple counter
	struct FontDescription *fd;	// Var-table
	unsigned long memattr;		// Attribute for bitmap-allocation

	// Demand memory for faked TextFont (that one we're going to create)
	ctsfont = (struct TextFont *) AllocMem( sizeof (struct TextFont), MEMF_CLEAR);
	if (ctsfont == NULL)
		return NULL;

	// Duplicate font-header
	CopyMem( font, ctsfont, sizeof( struct TextFont));


	// Demand memory for table of variables
	fd = (struct FontDescription *) AllocMem( sizeof (struct FontDescription), MEMF_CLEAR);
	if (fd == NULL)
	{
		FreeMem( ctsfont, sizeof (struct TextFont));
		return NULL;
	}

	fd->data = (unsigned char *) font->tf_CharData;		// Encoded pixels (PLANAR)
	fd->bytesperrow = font->tf_Modulo;					// Bytes in horizontal direction
	fd->lowchar = font->tf_LoChar;						// First encoded char
	fd->hichar = font->tf_HiChar;						// Last encoded char
	fd->charheight = font->tf_YSize;					// Height of all chars

	// Create a bitmap for the font we're going to use as input
	fd->bmap.BytesPerRow = fd->bytesperrow;
	fd->bmap.Rows = fd->charheight;
	fd->bmap.Flags = BMF_STANDARD;
	fd->bmap.Depth = 1;
	fd->bmap.pad = NULL;
	fd->bmap.Planes[0] = fd->data;

	// Set up a RastPort for the original font so we easily can use system functions to read its data
	InitRastPort( &fd->rport);
	fd->rport.BitMap = &fd->bmap;

	// Init a new rastport for the additional pixels (second font) so we can render...
	InitRastPort( &fd->destrp);
	// ...into this created bitmap
	CopyMem( &fd->bmap, &fd->destbm, sizeof (struct BitMap));


	// Figure out if the font-render data has to be placed in CHIP-Memory
	memattr = MEMF_CHIP;	// Default; use PLANAR-data in CHIP-Memory
	if (IntuitionBase->ActiveScreen)	// A screen opened?
	{
		if ( GfxBase->LibNode.lib_Version > 38)	// At least OS 3 ?
		{
			if ( !GetBitMapAttr( IntuitionBase->ActiveScreen->RastPort.BitMap, BMA_FLAGS) & BMF_STANDARD )
				memattr = NULL;	// Use FAST-Memory for PLANAR-data!
		}
	}


	// We need one additional bitmap (which becomes TextFont->tf_CharData) to render into
	fd->destbm.Planes[0] = (PLANEPTR) AllocMem( fd->bytesperrow * fd->charheight, memattr|MEMF_CLEAR);
	if (!fd->destbm.Planes[0])
	{
		FreeMem( fd, sizeof (struct FontDescription));
		FreeMem( ctsfont, sizeof (struct TextFont));
		return NULL;
	}

	// The TextFont-headers data pointer points to this new created bitmap
	ctsfont->tf_CharData = fd->destbm.Planes[0];
	fd->destrp.BitMap = &fd->destbm;

	farray = (struct CharArray *) font->tf_CharLoc;	// Pointer to each characters specific data

	textchar = 0;	// Start at first encoded character in bitmap

	while (textchar + fd->lowchar <= fd->hichar)
	{
		fd->charoffset = farray->CharIndex[textchar].offset;
		fd->charwidth =  farray->CharIndex[textchar].size;

		// Do nothing if this char cannot be modified!
		if ( (short) fd->charwidth > (short) 2)
			ModifyCharacter( fd);
		textchar ++;
	}

	// Release it - since we don't need it anymore
	FreeMem( fd, sizeof (struct FontDescription));

	ctsfont->tf_Accessors = 1;

	return ctsfont;
}

struct TextFont *CreateCatharsisFontA( REG(a0,struct TextFont *font))
{
	return CreateCatharsisFont( font);
}

/* ------------------------------------------------------------------------------------- */

struct TextFont *OpenCatharsisFont( struct TextFont *font, REG(a6, struct SharedLib *base))
{
	struct TextFont *found;
	ULONG i, emptyPtr;

	found = NULL;
	emptyPtr = -1L;
	i = 0;

	// Scan through list of remembered TextFonts to find the matching one
	while (i < 256 && !found)
	{
		if ( !base->sl_TextFont[i])	// Empty entry?
		{
			if (emptyPtr == -1L)	// No empty pointer remembered?
				emptyPtr = i;		// Remember first empty pointer
			i ++;					// Next
		}
		else	// It's non-empty
		{
			if (font == base->sl_TextFont[i])
			{
				found = base->sl_CatharsisFont[i];
				found->tf_Accessors ++;
			}
			else
			{
				i ++;	// Skip to next
			}
		}
	}

	// If this font is new, check also if there is a free entry useable
	if (!found && emptyPtr != -1L)
	{
		found = CreateCatharsisFont( font);
		if (found)
		{
			base->sl_TextFont[emptyPtr] = font;
			base->sl_CatharsisFont[emptyPtr] = found;
		}
	}

	return found;
}

struct TextFont *OpenCatharsisFontA( REG(a0, struct TextFont *font), REG(a6, struct SharedLib *base))
{
	return OpenCatharsisFont( font, base);
}

/* ---------------------------------------------------------------------------------- */

void CloseCatharsisFont( struct TextFont *font, REG(a6, struct SharedLib *base))
{
	ULONG i, found;

	font->tf_Accessors --;

	found = -1L;

	// Can we remove it?
	if ( (short) font->tf_Accessors < (short) 1)
	{
		i = 0;
		while (i < 256 && found == -1L)
		{
			if (font == base->sl_CatharsisFont[i])
				found = i;
			i ++;
		}

		if (found != -1L)
		{
			FreeMem( font->tf_CharData, font->tf_Modulo * font->tf_YSize);
			FreeMem( font, sizeof (struct TextFont));
			base->sl_TextFont[found] = NULL;
			base->sl_CatharsisFont[found] = NULL;
		}
	}
}

void CloseCatharsisFontA( REG(a0, struct TextFont *font), REG(a6, struct SharedLib *base))
{
	CloseCatharsisFont( font, base);
}

/*	-------------------------------------------------------------------------------------------------
	Compute for a colour value its corresponding catharsis value.
	Example:
		red bg colour = 27, green bg colour = 56, blue bg colour = 31
		red fg colour = 00, green fg colour = 37, blue fg colour = 08
		normal = set it to TRUE if background is lighter than foreground, otherwise set it to FALSE
	r = ComputeValue( 27, 00, 1);
	g = ComputeValue( 56, 37, 1);
	b = ComputeValue( 31, 08, 1);

*/

int ComputeCatharsisValue( int bg, int fg, int percent, int normal)
{
	int dist, retval;

	if (bg > fg)
		dist = bg - fg;
	else
		dist = fg - bg;

	// If distance is greater than halve of maximum luminance, darken or lighten pen-luminance
	if (percent >= 50)
	{
		if (normal)					// Background is lighter than foreground
		{
			if (bg - 23 >= 0)
				retval = bg - 23;	// Darken background by 'n' (here: 3 * bitspergun - 1)
			else
				retval = 0;			// Minimum possible
		}
		else						// Foreground lighter than background
		{
			if (fg > bg)
				retval = fg - (dist / 2);
			else
				retval = fg;
		}
	}
	else				// Interpol. of values
	{
		if (normal)
		{
			retval = (fg * (percent / 2)) / 100;
		}
		else
		{
			retval = (bg * (percent / 2)) / 100;
		}
	}

	if (retval < 0)
		retval = 0;
	if (retval > 255)
		retval = 255;

	return retval;
}

int ComputeCatharsisValueA( REG(d0, int bg), REG(d1, int fg), REG(d2, int percent), REG(d3, int normal))
{
	return ComputeCatharsisValue( bg, fg, percent, normal);
}

/* ---------------------------------------------------------------------------------- */

ULONG CompressColorValue( ULONG r, ULONG g, ULONG b)
{
	ULONG val;

	val =  (r & 0xFF000000L) >> 8;
	val |= (g & 0xFF000000L) >> 16;
	val |= (b & 0xFF000000L) >> 24;

	return val;
}

ULONG CompressColorValueA( REG(d0, ULONG r), REG(d1, ULONG g), REG(d2, ULONG b))
{
	return CompressColorValue( r, g, b);
}

/*	-------------------------------------------------------------------------------------------------
	Convert  a  8-bit  colour  value  into a AmigaOS compatible 32bit one: e.g. v = 139 (0x8B) - 8bit
	result:	v = 0x8BBBBBBB - 32bit
*/

ULONG CompatibleColorValue( ULONG v)
{
	ULONG help, nibble;

	nibble = help = (v & 0xF);				// 4bit
	nibble = help = (help << 4) | nibble;	// 8 bit (byte)
	help = (help << 8) | nibble;			// 16 bit (word)
	help = (help << 8) | nibble;			// 24 bit

	v = (v << 24);
	v |= help;		// longword
	return v;
}

ULONG CompatibleColorValueA( REG(d0, ULONG v))
{
	return CompatibleColorValue( v);
}

/*	-------------------------------------------------------------------------------------------------
	Compute a new colour-value basing on two that you passed in to this function.

	val1:	background  colour  value,  32bit,  where  the  leftmost  8  bits  will be ignore by this
			function since it is normally used as alpha channel:

			aaRRGGBB:	aa  -  8  bit alpha, RR - 8 bit red component, GG - 8 bit green component and
						BB - 8 bit blue component

	val2:	same as val1 but for foreground colour
	red:	pointer  to  a long-word variable to store a AmigaOS 32bit red component value, i.e. 8bit
			(RR)  0x8B  would  be  expanded  to  0x8BBBBBBB  ready  to  use by OS 3 graphic functions
			LoadRGB32(), FindColor() and so on.
			'red' may be zero, then the red-value is not stored
	green:	same as for red, but for the green component
	blue:	same as for red, but for the blue component

	RESULTS:
			Returns a compressed 32bit value, format aaRRGGBB in d0
			If  red,  green,  blue are non-zero, into the supplied variables the OS compatible colour
			values will be dropped

*/

ULONG CatharsisColorValue( ULONG val1, ULONG val2, ULONG *red, ULONG *green, ULONG *blue)
{
	ULONG val3, val4, bg[3], fg[3], r, g, b;

	bg[0] = ((val1 & 0xFF0000L) >> 16);	// Get red
	bg[1] = ((val1 & 0xFF00L) >> 8);	// Get green
	bg[2] = (val1 & 0xFFL);				// Get blue

	fg[0] = ((val2 & 0xFF0000L) >> 16);	// Get red
	fg[1] = ((val2 & 0xFF00L) >> 8);	// Get green
	fg[2] = (val2 & 0xFFL);				// Get blue


	/* Figure out if which is lighter (background or foreground) */
	val1 = ((bg[0] * 30) + (bg[1] * 59) + (bg[2] * 11)) / 100;
	val2 = ((fg[0] * 30) + (fg[1] * 59) + (fg[2] * 11)) / 100;

	if (val1 > val2)
	{
		val3 = TRUE;		// Normal display, background lighter than foreground
		val4 = val1 - val2;	// How much in per cent is the background lighter
	}
	else
	{
		val3 = FALSE;
		val4 = val2 - val1;	// How much in per cent is the foreground lighter
	}

	r = ComputeCatharsisValue( bg[0], fg[0], val4, val3);
	g = ComputeCatharsisValue( bg[1], fg[1], val4, val3);
	b = ComputeCatharsisValue( bg[2], fg[2], val4, val3);

	val3 = r;
	val3 = (val3 << 8);
	val3 |= g;
	val3 = (val3 << 8);
	val3 |= b;

	r = CompatibleColorValue( r);
	g = CompatibleColorValue( g);
	b = CompatibleColorValue( b);

	if (red)
		*red = r;
	if (green)
		*green = g;
	if (blue)
		*blue = b;

	return val3;
}

ULONG CatharsisColorValueA( REG(d0, ULONG val1), REG(d1, ULONG val2), REG(a0, ULONG *red), REG(a1, ULONG *green), REG(a2, ULONG *blue))
{
	return CatharsisColorValue( val1, val2, red, green, blue);
}

/* ------------------------------------------------------------------------------------------ */

void CatharsisText( struct RastPort *rp, unsigned char *str, unsigned int len, unsigned int pen, struct TextFont *ctsfont)
{
	unsigned short x, y, p;
	struct TextFont *f;

	x = rp->cp_x;
	y = rp->cp_y;
	p = rp->FgPen;
	f = rp->Font;

	Text( rp, str, len);

	if (ctsfont)
	{
		SetDrMd( rp, JAM1);
		SetFont( rp, ctsfont);
		Move( rp, x, y);
		SetAPen( rp, pen);
		Text( rp, str, len);
		SetFont( rp, f);
		SetAPen( rp, p);
	}
}

void CatharsisTextA( REG(a1,struct RastPort *rp), REG(a0,unsigned char *str), REG(d0,unsigned int len), REG(d1,unsigned int pen), REG(a2, struct TextFont *font))
{
	CatharsisText( rp, str, len, pen, font);
}

/* ---------------------------------------------------------------------------------- */
