Descent3/2dlib/font.cpp
2024-04-15 21:43:29 -06:00

796 lines
19 KiB
C++

/*
* $Logfile: /DescentIII/Main/2dlib/font.cpp $
* $Revision: 15 $
* $Date: 4/17/99 6:15p $
* $Author: Samir $
*
* will load and manage a font's capabilities.
*
* $Log: /DescentIII/Main/2dlib/font.cpp $
*
* 15 4/17/99 6:15p Samir
* replaced gr.h with grdefs.h and fixed resulting compile bugs.
*
* 14 10/13/98 10:31a Sean
* Fixed a mem_free (free()) conflict
*
* 13 4/23/98 6:38p Jason
* made bitmaps use 1555 format
*
* 12 3/31/98 3:48p Jason
* added memory lib
*
* 11 12/19/97 12:31p Samir
* load font registration
*
* 10 12/12/97 6:40p Samir
* Some viewport font functions.
*
* 9 11/16/97 6:53p Samir
* Reinstated instance count for fonts.
*
* 8 11/16/97 4:41p Matt
* Commented-out an assert.
*
* 7 11/14/97 12:06p Samir
* When you use a font for the first time, load it. Never free font until
* game closes.
*
* 6 10/15/97 5:20p Jason
* did a HUGE overhaul of the bitmap system
*
* 5 9/26/97 2:53p Samir
* Delete old m_CharSurf if creating a new one.
*
* 4 9/19/97 5:37p Samir
* Software font coloring works.
*
* 3 8/07/97 3:15p Samir
* May have fixed horizontal left clipping problem.
*
* 22 6/25/97 10:10a Samir
* Removed unused code causing warning.
*
* 21 6/24/97 7:42p Matt
* Included bitmap.h instead of font.h
*
* 20 6/16/97 4:45p Samir
* Fixed transparency probs
*
* 19 6/16/97 3:01p Samir
* Changed font system to work better with renderer. Fonts are rendered
* on bitmaps of 128x128 at load time.
*
* 18 6/12/97 6:27p Samir
* DDGR v2.0 (GR v1.1) Changes to ddgr system reflected in 2d system
*
* 17 5/14/97 8:07p Samir
* some pathname problem which screwed up Mac.
*
* 16 3/07/97 4:01p Samir
* Took out malloc.h for ANSI compliance.
*
* 15 3/03/97 6:20p Matt
* Changed cfile functions to use D3 naming convention
*
* 14 2/28/97 3:17 PM Jeremy
* changed a call of strcmpi to stricmp
*
* 13 2/28/97 2:57p Samir
* Took out __cdecl from atexit
*
* 12 2/10/97 12:14p Samir
* Allow multiple font loading per viewport.
*
* 11 2/06/97 6:33p Samir
* Added clipping character functions for font.
*
* 10 2/06/97 12:43p Samir
* Mono fonts work.
*
* 9 2/05/97 12:19p Samir
* Stopped redundant increases in font reference count for same font
* object.
*
* 8 2/05/97 10:35a Samir
* Fixed text drawing at wrong location problem
*
* $NoKeywords: $
*/
#include "pserror.h"
#include "renderer.h"
#include "gr.h"
#include "mono.h"
#include "CFILE.H"
#include "bitmap.h"
#include "mem.h"
#include <string.h>
#include <stdlib.h>
#define FT_COLOR 1
#define FT_PROPORTIONAL 2
#define FT_KERNED 4
#define FT_GRADIENT 8
#define GRFONT_SURFACE_WIDTH 128
#define GRFONT_SURFACE_HEIGHT 128
// ----------------------------------------------------------------------------
// Macros for file io.
// ----------------------------------------------------------------------------
typedef CFILE* FONTFILE;
#define BITS_TO_BYTES(_c) (((_c)+7)>>3)
#define BITS_TO_SHORTS(_c) (((_c)+15)>>4)
inline int READ_FONT_INT(FONTFILE ffile) {
return cf_ReadInt(ffile);
}
inline short READ_FONT_SHORT(FONTFILE ffile) {
return cf_ReadShort(ffile);
}
inline ubyte READ_FONT_BYTE(FONTFILE ffile) {
return (ubyte)cf_ReadByte(ffile);
}
inline int READ_FONT_DATA(FONTFILE ffile, void *buf, int size, int nelem)
{
int i;
i = cf_ReadBytes((ubyte *)buf, size*nelem, ffile);
ASSERT(i == (size*nelem));
return i;
}
inline FONTFILE OPEN_FONT(char *filename, bool &success)
{
FONTFILE fp;
int file_id;
success = false;
fp = (FONTFILE)cfopen(filename, "rb");
if (!fp) return NULL;
file_id = READ_FONT_INT(fp);
if (file_id != 0xfeedbaba) return (FONTFILE)(-1);
success = true;
return fp;
}
inline void CLOSE_FONT(FONTFILE ffile) {
cfclose(ffile);
}
// ----------------------------------------------------------------------------
// static variables and functions
// ----------------------------------------------------------------------------
gr_font_record grFont::m_FontList[MAX_FONTS];
grMemorySurface *grFont::m_CharSurf = NULL;
void grFont::init_system()
{
for (int i = 0; i < MAX_FONTS; i++)
{
grFont::m_FontList[i].references = 0;
grFont::m_FontList[i].filename[0] = 0;
}
if (grFont::m_CharSurf) {
delete grFont::m_CharSurf;
grFont::m_CharSurf = NULL;
}
if (!grFont::m_CharSurf) {
grFont::m_CharSurf = new grMemorySurface;
grFont::m_CharSurf->init(0,0,BPP_16, NULL, 0,0, "char_surf");
}
atexit(grFont::close_system);
}
void grFont::close_system()
{
if (grFont::m_CharSurf) {
delete grFont::m_CharSurf;
grFont::m_CharSurf = NULL;
}
// we should free any fonts currently allocated here.
mprintf((0, "Freeing cached fonts..."));
for (int i = 0; i < MAX_FONTS; i++)
{
gr_font_record *ft;
if (grFont::m_FontList[i].references) {
ft = &grFont::m_FontList[i];
grFont::free_font(ft);
grFont::m_FontList[i].references = 0;
}
}
}
int grFont::register_font(char *fontname, char *filename)
{
/* look for a slot with no fontname and place fontname in it */
ASSERT(fontname != NULL);
int i;
for (i = 0; i < MAX_FONTS; i++)
{
if (grFont::m_FontList[i].filename[0] == 0) {
strcpy(grFont::m_FontList[i].filename, filename);
strcpy(grFont::m_FontList[i].name, fontname);
grFont::load(m_FontList[i].filename, i);
grFont::m_FontList[i].references=1;
break;
}
}
if (i == MAX_FONTS) i = -1;
return i;
}
// ----------------------------------------------------------------------------
// constructor and destructor of font object
// ----------------------------------------------------------------------------
grFont::grFont()
{
m_FontHandle = -1;
}
grFont::~grFont()
{
if (m_FontHandle > -1)
grFont::free();
}
// ----------------------------------------------------------------------------
// high level font initialization and cleanup routines
// ----------------------------------------------------------------------------
void grFont::init(const char *fontname)
{
int i, slot = -1;
// Look for this font in the list, check if it has any references, and load it if
// there are none.
if (fontname == NULL) {
slot = 0;
}
else {
for (i = 0; i < MAX_FONTS; i++)
{
if (stricmp(grFont::m_FontList[i].name, fontname) == 0) {
slot = i;
if (grFont::m_FontList[i].references == 0) {
break;
}
else {
m_FontHandle = slot;
return;
}
}
}
ASSERT(slot != -1);
// clear up surface and bitmap lists
grFont::m_FontList[slot].ch_w = NULL;
grFont::m_FontList[slot].ch_h = NULL;
grFont::m_FontList[slot].ch_u = NULL;
grFont::m_FontList[slot].ch_v = NULL;
for (i = 0; i < MAX_FONT_BITMAPS; i++)
{
grFont::m_FontList[slot].bmps[i] = -1;
grFont::m_FontList[slot].surfs[i] = NULL;
}
// We must load this font from the 'hogfile'
grFont::load(m_FontList[slot].filename, slot);
grFont::m_FontList[slot].references++;
}
m_FontHandle = slot;
}
void grFont::free()
{
if (m_FontHandle == -1) return;
return;
//!! Removed the following assert because there are bugs in the reference count system. Matt, 11-15-97.
//!! ASSERT(grFont::m_FontList[m_FontHandle].references > 0);
//@@
// free font if there was only one reference left.
//@@ if (grFont::m_FontList[m_FontHandle].references == 1) {
//@@ gr_font_record *ft;
//@@
//@@ // free up all memory allocated to this font.
//@@ ft = &grFont::m_FontList[m_FontHandle];
//@@
//@@ grFont::free_font(ft);
//@@ }
//@@
//@@ grFont::m_FontList[m_FontHandle].references--;
m_FontHandle = -1;
}
void grFont::free_font(gr_font_record *ft)
{
int i;
// delete font surface info.
if (ft->ch_wf) delete[] ft->ch_wf;
if (ft->ch_hf) delete[] ft->ch_hf;
if (ft->ch_uf) delete[] ft->ch_uf;
if (ft->ch_vf) delete[] ft->ch_vf;
if (ft->ch_w) delete[] ft->ch_w;
if (ft->ch_h) delete[] ft->ch_h;
if (ft->ch_u) delete[] ft->ch_u;
if (ft->ch_v) delete[] ft->ch_v;
if (ft->ch_surf) delete[] ft->ch_surf;
for (i = 0; i < MAX_FONT_BITMAPS; i++)
{
if (ft->bmps[i] != -1) bm_FreeBitmap(ft->bmps[i]);
if (ft->surfs[i]) delete ft->surfs[i];
}
// delete font file info.
if (ft->font.flags & FT_PROPORTIONAL) {
delete[] ft->font.char_widths;
}
::mem_free(ft->font.raw_data);
::mem_free(ft->font.char_data);
}
void grFont::load(char *filename, int slot)
{
FONTFILE ff;
gr_font_file_record fnt, *ft;
char fontname[32];
int num_char;
int i;
ft = &fnt;
bool success;
ff = OPEN_FONT(filename, success);
if (!ff)
{
Error("Unable to open font in file %s.\n", filename);
}
else if ( !success )
{
Error("Illegal font file: %s.\n", filename);
}
mprintf((0, "%s font.\n", grFont::m_FontList[slot].name));
ft->width = READ_FONT_SHORT(ff);
ft->height = READ_FONT_SHORT(ff);
ft->flags = READ_FONT_SHORT(ff);
ft->baseline = READ_FONT_SHORT(ff);
ft->min_ascii = READ_FONT_BYTE(ff);
ft->max_ascii = READ_FONT_BYTE(ff);
READ_FONT_DATA(ff, fontname, 32, 1);
mprintf((0, " <ht %d>::<min %d>::<max %d>::<base %d>", ft->height, ft->min_ascii, ft->max_ascii, ft->baseline));
num_char = ft->max_ascii-ft->min_ascii+1;
// Read in all widths
if (ft->flags & FT_PROPORTIONAL) {
ft->char_widths = new short[num_char];
for (i = 0; i < num_char; i++)
ft->char_widths[i] = READ_FONT_SHORT(ff);
mprintf((0, "::proportional"));
}
else {
ft->char_widths = NULL;
}
// Read in pixel data.
// for color fonts, read in byte count and then the data,
// generate character data pointer table
// for mono fonts, read in byte count, then the data, convert to bits and store
// generate character data pointer table
int bytesize = READ_FONT_INT(ff);
ft->raw_data = (ubyte *)mem_malloc(bytesize);
ft->char_data = (ubyte **)mem_malloc(num_char * sizeof(ubyte *));
READ_FONT_DATA(ff, ft->raw_data, bytesize, 1);
if (ft->flags & FT_COLOR) {
int off = 0;
mprintf((0, "::color"));
for (i = 0; i < num_char; i++)
{
ft->char_data[i] = ft->raw_data + off;
if (ft->flags & FT_PROPORTIONAL)
off += (ft->char_widths[i]*ft->height*BITS_TO_BYTES(BPP_16));
else
off += (ft->width*ft->height*BITS_TO_BYTES(BPP_16));
}
}
else { // Monochrome
ubyte *ptr = ft->raw_data;
mprintf((0, "::mono"));
for (i = 0; i < num_char; i++)
{
ft->char_data[i] = ptr;
if (ft->flags & FT_PROPORTIONAL)
ptr += BITS_TO_BYTES(ft->char_widths[i]) * ft->height;
else
ptr += BITS_TO_BYTES(ft->width) * ft->height;
}
}
// Read in kerning data
ft->kern_data= NULL;
// Then read in
CLOSE_FONT(ff);
mprintf((0, "\n"));
grFont::m_FontList[slot].font = fnt;
// draw font to bitmaps, load into surfaces too.
grFont::translate_to_surfaces(slot);
}
// translates a font to surfaces
void grFont::translate_to_surfaces(int slot)
{
gr_font_file_record *fntfile;
gr_font_record *fnt;
fnt = &grFont::m_FontList[slot];
fntfile = &grFont::m_FontList[slot].font;
// start creating font surfaces, map these surfaces onto bitmaps created via bitmap library
// this is needed for the renderer library.
// create a 128x128 surface first.
// draw each character into surface until we need to create another
// surface.
ubyte u=0, v=0, w;
int ch, num_ch;
ubyte surf_index = 0;
num_ch = fntfile->max_ascii-fntfile->min_ascii+1;
// initialize memory
fnt->ch_w = new ubyte[num_ch];
fnt->ch_h = new ubyte[num_ch];
fnt->ch_u = new ubyte[num_ch];
fnt->ch_v = new ubyte[num_ch];
fnt->ch_surf = new ubyte[num_ch];
fnt->ch_uf = new float[num_ch];
fnt->ch_vf = new float[num_ch];
fnt->ch_wf = new float[num_ch];
fnt->ch_hf = new float[num_ch];
fnt->bmps[surf_index] = bm_AllocBitmap(GRFONT_SURFACE_WIDTH, GRFONT_SURFACE_HEIGHT, 0);
if (fnt->bmps[surf_index] == -1) Error("grFont::translate_to_surfaces <Bitmap allocation error>");
fnt->surfs[surf_index] = new grMemorySurface;
fnt->surfs[surf_index]->init(GRFONT_SURFACE_WIDTH, GRFONT_SURFACE_HEIGHT, BPP_16,
(char *)bm_data(fnt->bmps[surf_index],0),
GRFONT_SURFACE_WIDTH * BITS_TO_BYTES(BPP_16),
SURFFLAG_COLORKEY);
fnt->surfs[surf_index]->clear(TRANSPARENT_COLOR32);
surf_index++;
for (ch = 0; ch < num_ch; ch++)
{
if (fntfile->flags & FT_PROPORTIONAL) w = (int)fntfile->char_widths[ch];
else w = (int)fntfile->width;
// blt each character
if (fntfile->flags & FT_COLOR) {
m_CharSurf->init(w, fntfile->height, BPP_16, (char *)fntfile->char_data[ch],
w*BITS_TO_BYTES(BPP_16), SURFFLAG_COLORKEY);
fnt->surfs[surf_index-1]->blt(u,v,m_CharSurf);
}
else { // font monochrome, convert bits to pixels
grFont::translate_mono_char(fnt->surfs[surf_index-1], u,v,ch,fntfile,w);
}
fnt->ch_h[ch] = fntfile->height;
fnt->ch_w[ch] = w;
fnt->ch_u[ch] = u;
fnt->ch_v[ch] = v;
fnt->ch_surf[ch] = surf_index-1;
fnt->ch_hf[ch] = ((float)fntfile->height)/((float)GRFONT_SURFACE_HEIGHT);
fnt->ch_wf[ch] = ((float)w)/((float)GRFONT_SURFACE_WIDTH);
fnt->ch_uf[ch] = ((float)u)/((float)GRFONT_SURFACE_WIDTH);
fnt->ch_vf[ch] = ((float)v)/((float)GRFONT_SURFACE_HEIGHT);
// check to adjust uv's if we are outside surface.
u+= w;
if ((u+w) > GRFONT_SURFACE_WIDTH) {
u = 0;
v += fntfile->height;
if ((v+fntfile->height) > GRFONT_SURFACE_HEIGHT) {
if (surf_index == MAX_FONT_BITMAPS) Int3();
fnt->bmps[surf_index] = bm_AllocBitmap(GRFONT_SURFACE_WIDTH, GRFONT_SURFACE_HEIGHT, 0);
if (fnt->bmps[surf_index] == -1) Error("grFont::translate_to_surfaces <Bitmap allocation error>");
fnt->surfs[surf_index] = new grMemorySurface;
fnt->surfs[surf_index]->init(GRFONT_SURFACE_WIDTH, GRFONT_SURFACE_HEIGHT, BPP_16,
(char*)bm_data(fnt->bmps[surf_index],0),
GRFONT_SURFACE_WIDTH * BITS_TO_BYTES(BPP_16),
SURFFLAG_COLORKEY);
fnt->surfs[surf_index]->clear(TRANSPARENT_COLOR32);
surf_index++;
v = 0;
}
}
}
}
// translate mono font data to the surface
void grFont::translate_mono_char(grSurface *sf, int x, int y, int index, gr_font_file_record *ft,
int width)
{
int row,col; // byte width of char
int rowsize;
ubyte bit_mask=0, byte;
ubyte *fp;
fp = ft->char_data[index];
switch (sf->bpp())
{
case BPP_16:
{
/* draw one-bit one color. */
ushort *dest_ptr;
ushort col_w = GR_COLOR_TO_16(GR_RGB(255,255,255));
int rowsize_w;
dest_ptr = (ushort *)sf->lock(&rowsize);
rowsize_w = sf->rowsize()/2;
dest_ptr += (y*rowsize_w)+x;
for (row = 0; row < ft->height; row++)
{
bit_mask = 0;
for (col = 0; col < width; col++)
{
if (bit_mask == 0) {
byte = *fp++;
bit_mask = 0x80;
}
if (byte & bit_mask)
dest_ptr[col] = col_w;
bit_mask >>=1;
}
dest_ptr += rowsize_w;
}
sf->unlock();
break;
}
case BPP_32:
default:
Int3(); // NOT SUPPORTED YET
}
}
// ----------------------------------------------------------------------------
// draw_char function
// give a viewport x,y and char
// ----------------------------------------------------------------------------
int grFont::draw_char(grSurface *sf, int x, int y, int ch, tCharProperties *chprop)
{
gr_font_record *ft;
int next_x, width, index;
ASSERT(m_FontHandle > -1);
if ((ch < min_ascii()) || (ch > max_ascii()))
return (next_x+1);
// save current x and get this font
ft = &grFont::m_FontList[m_FontHandle];
next_x = x;
index = ch - ft->font.min_ascii;
// draw either a color bitmap or mono font.
if (ft->font.flags & FT_PROPORTIONAL) {
width = (int)ft->font.char_widths[index];
}
else {
width = (int)ft->font.width;
}
// perform blt.
if (sf->get_flags() & SURFFLAG_RENDERER) {
rend_SetCharacterParameters(chprop->col[0],chprop->col[1],chprop->col[2],chprop->col[3]);
rend_DrawFontCharacter (ft->bmps[ft->ch_surf[index]], x,y,x+ft->ch_w[index]-1,y+ft->ch_h[index]-1,
ft->ch_uf[index], ft->ch_vf[index], ft->ch_wf[index], ft->ch_hf[index]);
}
else if (ft->font.flags & FT_COLOR) {
sf->blt(x,y,ft->surfs[ft->ch_surf[index]],
ft->ch_u[index], ft->ch_v[index],
ft->ch_w[index], ft->ch_h[index]);
}
else {
charblt16(sf, chprop->col[0], x,y, ft->surfs[ft->ch_surf[index]],
ft->ch_u[index], ft->ch_v[index],
ft->ch_w[index], ft->ch_h[index]);
}
// adjust next x value with kerning and return it.
next_x += width;
return next_x;
}
int grFont::draw_char(grSurface *sf, int x, int y, int ch, int sx, int sy, int sw, int sh, tCharProperties *chprop)
{
gr_font_record *ft;
int next_x, width, index;
ASSERT(m_FontHandle > -1);
if ((ch < min_ascii()) || (ch > max_ascii()))
return (next_x+1);
// save current x and get this font
ft = &grFont::m_FontList[m_FontHandle];
next_x = x;
index = ch - ft->font.min_ascii;
// draw either a color bitmap or mono font.
if (ft->font.flags & FT_PROPORTIONAL) {
width = (int)ft->font.char_widths[index];
}
else {
width = (int)ft->font.width;
}
ASSERT(sx+sw <= width);
ASSERT(sy+sh <= ft->font.height);
// peform blt instead.
// perform blt.
if (sf->get_flags() & SURFFLAG_RENDERER) {
rend_SetCharacterParameters(chprop->col[0],chprop->col[1],chprop->col[2],chprop->col[3]);
rend_DrawFontCharacter (ft->bmps[ft->ch_surf[index]], x,y,x+sw-1,y+sh-1,
ft->ch_uf[index]+(((float)sx)/((float)GRFONT_SURFACE_WIDTH)),
ft->ch_vf[index]+(((float)sy)/((float)GRFONT_SURFACE_HEIGHT)),
((float)sw)/((float)GRFONT_SURFACE_WIDTH),
((float)sh)/((float)GRFONT_SURFACE_HEIGHT));
}
else if (ft->font.flags & FT_COLOR) {
sf->blt(x,y,ft->surfs[ft->ch_surf[index]],
ft->ch_u[index]+sx, ft->ch_v[index]+sy,
sw, sh);
}
else {
charblt16(sf, chprop->col[0], x,y,ft->surfs[ft->ch_surf[index]],
ft->ch_u[index]+sx, ft->ch_v[index]+sy,
sw, sh);
}
// adjust next x value with kerning and return it.
next_x += sw;
return next_x;
}
// ----------------------------------------------------------------------------
// accessor functions
// ----------------------------------------------------------------------------
int grFont::get_char_info(int ch, int *width)
{
gr_font_file_record *ft;
ASSERT(m_FontHandle > -1);
ft = &grFont::m_FontList[m_FontHandle].font;
if (ch < ft->min_ascii || ch > ft->max_ascii)
*width = 0;
else if (ft->flags & FT_PROPORTIONAL)
*width = ft->char_widths[ch-ft->min_ascii];
else
*width = ft->width;
return 0;
}
ubyte *grFont::get_kern_info(ubyte c1, ubyte c2)
{
// gr_font_file_record *ft;
// ubyte *p;
// p = ft->kern_data;
// ft = &grFont::m_FontList[m_FontHandle].font;
// c2 = c2 - ft->min_ascii;
// c1 = c1 - ft->min_ascii;
// while (*p!=255)
// {
// if (p[0]==c1 && p[1]==c2) return p;
// else p+=3;
// }
return NULL;
}
void grFont::charblt16(grSurface *dsf, ddgr_color col, int dx, int dy, grSurface *ssf, int sx, int sy, int sw, int sh)
{
ushort *dbits;
ushort *sbits;
int srowsize_w, drowsize_w, row, coln;
ushort scol = GR_COLOR_TO_16(col);
dbits = (ushort *)dsf->lock(&drowsize_w);
if (dbits) {
sbits = (ushort *)ssf->lock(&srowsize_w);
if (sbits) {
srowsize_w >>= 1; // rowsize in shorts
drowsize_w >>= 1;
dbits = dbits + (dy * drowsize_w) + dx;
sbits = sbits + (sy * srowsize_w) + sx;
for (row = 0; row < sh; row++)
{
for (coln = 0; coln < sw; coln++)
{
if (sbits[coln] & OPAQUE_FLAG16)
dbits[coln] = scol;
}
sbits += srowsize_w;
dbits += drowsize_w;
}
ssf->unlock();
}
dsf->unlock();
}
}