/* ScummVM - Scumm Interpreter * Copyright (C) 2004 The ScummVM project * * The ReInherit Engine is (C)2000-2003 by Daniel Balsom. * * 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 2 * 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, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * $Header$ * */ // Font management and font drawing module #include "saga.h" #include "reinherit.h" #include "rscfile_mod.h" #include "game_mod.h" #include "font_mod.h" #include "font.h" namespace Saga { static R_FONT_MODULE FontModule; int FONT_Init() { R_GAME_FONTDESC *gamefonts; int i; if (FontModule.init) { FontModule.err_str = "Font module already initialized."; return R_FAILURE; } // Load font module resource context if (GAME_GetFileContext(&FontModule.font_ctxt, R_GAME_RESOURCEFILE, 0) != R_SUCCESS) { FontModule.err_str = "Couldn't get resource context."; return R_FAILURE; } // Allocate font table GAME_GetFontInfo(&gamefonts, &FontModule.n_fonts); assert(FontModule.n_fonts > 0); FontModule.fonts = (R_FONT **)malloc(FontModule.n_fonts * sizeof *FontModule.fonts); if (FontModule.fonts == NULL) { FontModule.err_str = "Memory allocation failure."; return R_MEM; } for (i = 0; i < FontModule.n_fonts; i++) { FONT_Load(gamefonts[i].font_rn, gamefonts[i].font_id); } FontModule.init = 1; return R_SUCCESS; } int FONT_Shutdown() { // int i; debug(0, "FONT_Shutdown(): Freeing fonts."); /* for ( i = 0 ; i < R_FONT_COUNT ; i ++ ) { if ( FontModule.fonts[i] != NULL ) { if ( FontModule.fonts[i]->normal_loaded ) { free( FontModule.fonts[i]->normal->font_free_p ); free( FontModule.fonts[i]->normal ); } if ( FontModule.fonts[i]->outline_loaded ) { free( FontModule.fonts[i]->outline->font_free_p ); free( FontModule.fonts[i]->outline ); } } free( FontModule.fonts[i] ); } */ return R_SUCCESS; } int FONT_Load(uint32 font_rn, int font_id) { R_FONT_HEADER fh; R_FONT *font; R_FONT_STYLE *normal_font; byte *fontres_p; size_t fontres_len; int nbits; int c; if ((font_id < 0) || (font_id >= FontModule.n_fonts)) { return R_FAILURE; } // Load font resource if (RSC_LoadResource(FontModule.font_ctxt, font_rn, &fontres_p, &fontres_len) != R_SUCCESS) { FontModule.err_str = "Couldn't load font resource."; return R_FAILURE; } if (fontres_len < R_FONT_DESCSIZE) { FontModule.err_str = "Invalid font length."; } MemoryReadStream *readS = new MemoryReadStream(fontres_p, fontres_len); // Create new font structure font = (R_FONT *)malloc(sizeof *font); if (font == NULL) { FontModule.err_str = "Memory allocation error."; return R_MEM; } // Read font header fh.c_height = readS->readUint16LE(); fh.c_width = readS->readUint16LE(); fh.row_length = readS->readUint16LE(); debug(1, "FONT_Load(): Reading font resource..."); debug(2, "Character width:\t%d", fh.c_width); debug(2, "Character height:\t%d", fh.c_height); debug(2, "Row padding:\t%d", fh.row_length); // Create normal font style normal_font = (R_FONT_STYLE *)malloc(sizeof *normal_font); if (normal_font == NULL) { FontModule.err_str = "Memory allocation error."; free(font); return R_MEM; } normal_font->font_free_p = fontres_p; normal_font->hdr.c_height = fh.c_height; normal_font->hdr.c_width = fh.c_width; normal_font->hdr.row_length = fh.row_length; for (c = 0; c < R_FONT_CHARCOUNT; c++) { normal_font->fce[c].index = readS->readUint16LE(); } for (c = 0; c < R_FONT_CHARCOUNT; c++) { nbits = normal_font->fce[c].width = readS->readByte(); normal_font->fce[c].byte_width = GetByteLen(nbits); } for (c = 0; c < R_FONT_CHARCOUNT; c++) { normal_font->fce[c].flag = readS->readByte(); } for (c = 0; c < R_FONT_CHARCOUNT; c++) { normal_font->fce[c].tracking = readS->readByte(); } if (readS->tell() != R_FONT_DESCSIZE) { warning("Invalid font resource size."); return R_FAILURE; } normal_font->font_p = fontres_p + R_FONT_DESCSIZE; font->normal = normal_font; font->normal_loaded = 1; // Create outline font style font->outline = FONT_CreateOutline(normal_font); font->outline_loaded = 1; // Set font data FontModule.fonts[font_id] = font; return R_SUCCESS; } int FONT_GetHeight(int font_id) { R_FONT *font; if (!FontModule.init) { return R_FAILURE; } if ((font_id < 0) || (font_id >= FontModule.n_fonts) || (FontModule.fonts[font_id] == NULL)) { FontModule.err_str = "Invalid font id."; return R_FAILURE; } font = FontModule.fonts[font_id]; return font->normal->hdr.c_height; } static R_FONT_STYLE *FONT_CreateOutline(R_FONT_STYLE *src_font) { R_FONT_STYLE *new_font; unsigned char *new_font_data; size_t new_font_data_len; int s_width = src_font->hdr.c_width; int s_height = src_font->hdr.c_height; int new_row_len = 0; int row; int i; int index; size_t index_offset = 0; int new_byte_width; int old_byte_width; int current_byte; unsigned char *base_ptr; unsigned char *src_ptr; unsigned char *dest_ptr1; unsigned char *dest_ptr2; unsigned char *dest_ptr3; unsigned char c_rep; // Create new font style structure new_font = (R_FONT_STYLE *)malloc(sizeof *new_font); if (new_font == NULL) { FontModule.err_str = "Memory allocation error."; return NULL; } memset(new_font, 0, sizeof *new_font); // Populate new font style character data for (i = 0; i < R_FONT_CHARCOUNT; i++) { new_byte_width = 0; old_byte_width = 0; index = src_font->fce[i].index; if ((index > 0) || (i == R_FONT_FIRSTCHAR)) { index += index_offset; } new_font->fce[i].index = index; new_font->fce[i].tracking = src_font->fce[i].tracking; new_font->fce[i].flag = src_font->fce[i].flag; if (src_font->fce[i].width != 0) { new_byte_width = GetByteLen(src_font->fce[i].width + 2); old_byte_width = GetByteLen(src_font->fce[i].width); if (new_byte_width > old_byte_width) { index_offset++; } } new_font->fce[i].width = src_font->fce[i].width + 2; new_font->fce[i].byte_width = new_byte_width; new_row_len += new_byte_width; } debug(2, "New row length: %d", new_row_len); new_font->hdr.c_width = s_width + 2; new_font->hdr.c_height = s_height + 2; new_font->hdr.row_length = new_row_len; // Allocate new font representation storage new_font_data_len = new_row_len * (s_height + 2); new_font_data = (unsigned char *)malloc(new_font_data_len); if (new_font_data == NULL) { FontModule.err_str = "Memory allocation error."; return NULL; } memset(new_font_data, 0, new_font_data_len); new_font->font_free_p = new_font_data; new_font->font_p = new_font_data; // Generate outline font representation for (i = 0; i < R_FONT_CHARCOUNT; i++) { for (row = 0; row < s_height; row++) { for (current_byte = 0; current_byte < new_font->fce[i].byte_width; current_byte++) { base_ptr = new_font->font_p + new_font->fce[i].index + current_byte; dest_ptr1 = base_ptr + new_font->hdr.row_length * row; dest_ptr2 = base_ptr + new_font->hdr.row_length * (row + 1); dest_ptr3 = base_ptr + new_font->hdr.row_length * (row + 2); if (current_byte > 0) { // Get last two columns from previous byte src_ptr = src_font->font_p + src_font->hdr.row_length * row + src_font->fce[i].index + (current_byte - 1); c_rep = *src_ptr; *dest_ptr1 |= ((c_rep << 6) | (c_rep << 7)); *dest_ptr2 |= ((c_rep << 6) | (c_rep << 7)); *dest_ptr3 |= ((c_rep << 6) | (c_rep << 7)); } if (current_byte < src_font->fce[i].byte_width) { src_ptr = src_font->font_p + src_font->hdr.row_length * row + src_font->fce[i].index + current_byte; c_rep = *src_ptr; *dest_ptr1 |= c_rep | (c_rep >> 1) | (c_rep >> 2); *dest_ptr2 |= c_rep | (c_rep >> 1) | (c_rep >> 2); *dest_ptr3 |= c_rep | (c_rep >> 1) | (c_rep >> 2); } } } // "Hollow out" character to prevent overdraw for (row = 0; row < s_height; row++) { for (current_byte = 0; current_byte < new_font->fce[i].byte_width; current_byte++) { dest_ptr2 = new_font->font_p + new_font->hdr.row_length * (row + 1) + new_font->fce[i].index + current_byte; if (current_byte > 0) { // Get last two columns from previous byte src_ptr = src_font->font_p + src_font->hdr.row_length * row + src_font->fce[i].index + (current_byte - 1); *dest_ptr2 &= ((*src_ptr << 7) ^ 0xFFU); } if (current_byte < src_font->fce[i].byte_width) { src_ptr = src_font->font_p + src_font->hdr.row_length * row + src_font->fce[i].index + current_byte; *dest_ptr2 &= ((*src_ptr >> 1) ^ 0xFFU); } } } } return new_font; } static int GetByteLen(int num_bits) { int byte_len; byte_len = num_bits / 8; if (num_bits % 8) { byte_len++; } return byte_len; } // Returns the horizontal length in pixels of the graphical representation // of at most 'test_str_ct' characters of the string 'test_str', taking // into account any formatting options specified by 'flags'. // If 'test_str_ct' is 0, all characters of 'test_str' are counted. int FONT_GetStringWidth(int font_id, const char *test_str, size_t test_str_ct, int flags) { R_FONT *font; size_t ct; int width = 0; int ch; const byte *txt_p; if (!FontModule.init) { return R_FAILURE; } if ((font_id < 0) || (font_id >= FontModule.n_fonts) || (FontModule.fonts[font_id] == NULL)) { FontModule.err_str = "Invalid font id."; return R_FAILURE; } font = FontModule.fonts[font_id]; assert(font != NULL); txt_p = (const byte *) test_str; for (ct = test_str_ct; *txt_p && (!test_str_ct || ct > 0); txt_p++, ct--) { ch = *txt_p & 0xFFU; // Translate character ch = CharMap[ch]; assert(ch < R_FONT_CHARCOUNT); width += font->normal->fce[ch].tracking; } if ((flags & FONT_BOLD) || (flags & FONT_OUTLINE)) { width += 1; } return width; } int FONT_Draw(int font_id, R_SURFACE *ds, const char *draw_str, size_t draw_str_ct, int text_x, int text_y, int color, int effect_color, int flags) { R_FONT *font; if (!FontModule.init) { FontModule.err_str = "Font Module not initialized."; return R_FAILURE; } if ((font_id < 0) || (font_id >= FontModule.n_fonts) || (FontModule.fonts[font_id] == NULL)) { FontModule.err_str = "Invalid font id."; return R_FAILURE; } font = FontModule.fonts[font_id]; if (flags & FONT_OUTLINE) { FONT_Out(font->outline, ds, draw_str, draw_str_ct, text_x - 1, text_y - 1, effect_color); FONT_Out(font->normal, ds, draw_str, draw_str_ct, text_x, text_y, color); } else if (flags & FONT_SHADOW) { FONT_Out(font->normal, ds, draw_str, draw_str_ct, text_x - 1, text_y + 1, effect_color); FONT_Out(font->normal, ds, draw_str, draw_str_ct, text_x, text_y, color); } else { // FONT_NORMAL FONT_Out(font->normal, ds, draw_str, draw_str_ct, text_x, text_y, color); } return R_SUCCESS; } int FONT_Out(R_FONT_STYLE * draw_font, R_SURFACE * ds, const char *draw_str, size_t draw_str_ct, int text_x, int text_y, int color) { const byte *draw_str_p; byte *c_data_ptr; int c_code; int char_row; byte *output_ptr; byte *output_ptr_min; byte *output_ptr_max; int row; int row_limit; int c_byte_len; int c_byte; int c_bit; int ct; if ((text_x > ds->buf_w) || (text_y > ds->buf_h)) { // Output string can't be visible return R_SUCCESS; } draw_str_p = (const byte *) draw_str; ct = draw_str_ct; // Draw string one character at a time, maximum of 'draw_str'_ct // characters, or no limit if 'draw_str_ct' is 0 for (; *draw_str_p && (!draw_str_ct || ct); draw_str_p++, ct--) { c_code = *draw_str_p & 0xFFU; // Translate character c_code = CharMap[c_code]; assert(c_code < R_FONT_CHARCOUNT); // Check if character is defined if ((draw_font->fce[c_code].index == 0) && (c_code != R_FONT_FIRSTCHAR)) { #if R_FONT_SHOWUNDEFINED if (c_code == R_FONT_CH_SPACE) { text_x += draw_font->fce[c_code].tracking; continue; } c_code = R_FONT_CH_QMARK; #else // Character code is not defined, but advance tracking // ( Not defined if offset is 0, except for 33 ('!') which // is defined ) text_x += draw_font->fce[c_code].tracking; continue; #endif } // Get length of character in bytes c_byte_len = ((draw_font->fce[c_code].width - 1) / 8) + 1; row_limit = (ds->buf_h < (text_y + draw_font->hdr.c_height)) ? ds->buf_h : text_y + draw_font->hdr.c_height; char_row = 0; for (row = text_y; row < row_limit; row++, char_row++) { // Clip negative rows */ if (row < 0) { continue; } output_ptr = ds->buf + (ds->buf_pitch * row) + text_x; output_ptr_min = ds->buf + (ds->buf_pitch * row) + (text_x > 0 ? text_x : 0); output_ptr_max = output_ptr + (ds->buf_pitch - text_x); // If character starts off the screen, jump to next character if (output_ptr < output_ptr_min) { break; } c_data_ptr = draw_font->font_p + char_row * draw_font->hdr.row_length + draw_font->fce[c_code].index; for (c_byte = 0; c_byte < c_byte_len; c_byte++, c_data_ptr++) { // Check each bit, draw pixel if bit is set for (c_bit = 7; c_bit >= 0 && (output_ptr < output_ptr_max); c_bit--) { if ((*c_data_ptr >> c_bit) & 0x01) { *output_ptr = (byte) color; } output_ptr++; } // end per-bit processing } // end per-byte processing } // end per-row processing // Advance tracking position text_x += draw_font->fce[c_code].tracking; } // end per-character processing return R_SUCCESS; } } // End of namespace Saga