/* * Descent 3 * Copyright (C) 2024 Parallax Software * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . --- HISTORICAL COMMENTS FOLLOW --- * $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 #include #include "bitmap.h" #include "cfile.h" #include "gr.h" #include "log.h" #include "mem.h" #include "pserror.h" #include "renderer.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; static inline int READ_FONT_INT(FONTFILE ffile); static inline int16_t READ_FONT_SHORT(FONTFILE ffile); static inline uint8_t READ_FONT_BYTE(FONTFILE ffile); static inline int READ_FONT_DATA(FONTFILE ffile, void *buf, int size, int nelem); static inline FONTFILE OPEN_FONT(const char *filename, bool &success); static inline void CLOSE_FONT(FONTFILE ffile); #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 int16_t READ_FONT_SHORT(FONTFILE ffile) { return cf_ReadShort(ffile); } inline uint8_t READ_FONT_BYTE(FONTFILE ffile) { return (uint8_t)cf_ReadByte(ffile); } inline int READ_FONT_DATA(FONTFILE ffile, void *buf, int size, int nelem) { int i; i = cf_ReadBytes((uint8_t *)buf, size * nelem, ffile); ASSERT(i == (size * nelem)); return i; } inline FONTFILE OPEN_FONT(const 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. LOG_DEBUG << "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); } 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); num_char = ft->max_ascii - ft->min_ascii + 1; // Read in all widths if (ft->flags & FT_PROPORTIONAL) { ft->char_widths = new int16_t[num_char]; for (i = 0; i < num_char; i++) ft->char_widths[i] = READ_FONT_SHORT(ff); } 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 = mem_rmalloc(bytesize); ft->char_data = mem_rmalloc(num_char); READ_FONT_DATA(ff, ft->raw_data, bytesize, 1); if (ft->flags & FT_COLOR) { int off = 0; 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 uint8_t *ptr = ft->raw_data; 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); LOG_DEBUG.printf("%s font: ::::::::::", grFont::m_FontList[slot].name, ft->height, ft->min_ascii, ft->max_ascii, ft->baseline, ft->flags & FT_PROPORTIONAL, ft->flags & FT_COLOR); 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. uint8_t u = 0, v = 0, w; int ch, num_ch; uint8_t surf_index = 0; num_ch = fntfile->max_ascii - fntfile->min_ascii + 1; // initialize memory fnt->ch_w = new uint8_t[num_ch]; fnt->ch_h = new uint8_t[num_ch]; fnt->ch_u = new uint8_t[num_ch]; fnt->ch_v = new uint8_t[num_ch]; fnt->ch_surf = new uint8_t[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 "); 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 "); 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; uint8_t bit_mask = 0, byte = 0; uint8_t *fp; fp = ft->char_data[index]; switch (sf->bpp()) { case BPP_16: { /* draw one-bit one color. */ uint16_t *dest_ptr; uint16_t col_w = GR_COLOR_TO_16(GR_RGB(255, 255, 255)); int rowsize_w; dest_ptr = (uint16_t *)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 = 0, 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 = 0, 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; } uint8_t *grFont::get_kern_info(uint8_t c1, uint8_t c2) { // gr_font_file_record *ft; // uint8_t *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) { uint16_t *dbits; uint16_t *sbits; int srowsize_w, drowsize_w, row, coln; uint16_t scol = GR_COLOR_TO_16(col); dbits = (uint16_t *)dsf->lock(&drowsize_w); if (dbits) { sbits = (uint16_t *)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(); } }