diff options
author | Max Horn | 2006-02-11 22:45:04 +0000 |
---|---|---|
committer | Max Horn | 2006-02-11 22:45:04 +0000 |
commit | 26ee630756ebdd7c96bccede0881a8c8b98e8f2b (patch) | |
tree | 26e378d5cf990a2b81c2c96e9e683a7f333b62e8 /engines/saga/font.cpp | |
parent | 2a9a0d4211b1ea5723f1409d91cb95de8984429e (diff) | |
download | scummvm-rg350-26ee630756ebdd7c96bccede0881a8c8b98e8f2b.tar.gz scummvm-rg350-26ee630756ebdd7c96bccede0881a8c8b98e8f2b.tar.bz2 scummvm-rg350-26ee630756ebdd7c96bccede0881a8c8b98e8f2b.zip |
Moved engines to the new engines/ directory
svn-id: r20582
Diffstat (limited to 'engines/saga/font.cpp')
-rw-r--r-- | engines/saga/font.cpp | 681 |
1 files changed, 681 insertions, 0 deletions
diff --git a/engines/saga/font.cpp b/engines/saga/font.cpp new file mode 100644 index 0000000000..9a91bf4872 --- /dev/null +++ b/engines/saga/font.cpp @@ -0,0 +1,681 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2004-2006 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id$ + * + */ + +// Font management and font drawing module +#include "saga/saga.h" +#include "saga/gfx.h" +#include "saga/rscfile.h" + +#include "saga/font.h" +#include "saga/stream.h" + +namespace Saga { + +Font::Font(SagaEngine *vm) : _vm(vm), _initialized(false) { + int i; + + // Load font module resource context + + assert(_vm->getFontsCount() > 0); + + _fonts = (FontData **)calloc(_vm->getFontsCount(), sizeof(*_fonts)); + _loadedFonts = 0; + + for (i = 0; i < _vm->getFontsCount(); i++) { + loadFont(_vm->getFontDescription(i)->fontResourceId); + } + + _initialized = true; +} + +Font::~Font(void) { + debug(8, "Font::~Font(): Freeing fonts."); + int i; + + for (i = 0 ; i < _loadedFonts ; i++) { + if (_fonts[i] != NULL) { + free(_fonts[i]->normal.font); + free(_fonts[i]->outline.font); + } + + free(_fonts[i]); + } +} + + +void Font::loadFont(uint32 fontResourceId) { + FontData *font; + byte *fontResourcePointer; + size_t fontResourceLength; + int numBits; + int c; + ResourceContext *fontContext; + + debug(1, "Font::loadFont(): Reading fontResourceId %d...", fontResourceId); + + fontContext = _vm->_resource->getContext(GAME_RESOURCEFILE); + if (fontContext == NULL) { + error("Font::Font() resource context not found"); + } + + // Load font resource + _vm->_resource->loadResource(fontContext, fontResourceId, fontResourcePointer, fontResourceLength); + + if (fontResourceLength < FONT_DESCSIZE) { + error("Font::loadFont() Invalid font length (%i < %i)", fontResourceLength, FONT_DESCSIZE); + } + + MemoryReadStreamEndian readS(fontResourcePointer, fontResourceLength, fontContext->isBigEndian); + + // Create new font structure + font = (FontData *)malloc(sizeof(*font)); + + // Read font header + font->normal.header.charHeight = readS.readUint16(); + font->normal.header.charWidth = readS.readUint16(); + font->normal.header.rowLength = readS.readUint16(); + + + debug(2, "Character width: %d", font->normal.header.charWidth); + debug(2, "Character height: %d", font->normal.header.charHeight); + debug(2, "Row padding: %d", font->normal.header.rowLength); + + for (c = 0; c < FONT_CHARCOUNT; c++) { + font->normal.fontCharEntry[c].index = readS.readUint16(); + } + + for (c = 0; c < FONT_CHARCOUNT; c++) { + numBits = font->normal.fontCharEntry[c].width = readS.readByte(); + font->normal.fontCharEntry[c].byteWidth = getByteLen(numBits); + } + + for (c = 0; c < FONT_CHARCOUNT; c++) { + font->normal.fontCharEntry[c].flag = readS.readByte(); + } + + for (c = 0; c < FONT_CHARCOUNT; c++) { + font->normal.fontCharEntry[c].tracking = readS.readByte(); + } + + if (readS.pos() != FONT_DESCSIZE) { + error("Invalid font resource size."); + } + + font->normal.font = (byte*)malloc(fontResourceLength - FONT_DESCSIZE); + memcpy(font->normal.font, fontResourcePointer + FONT_DESCSIZE, fontResourceLength - FONT_DESCSIZE); + + free(fontResourcePointer); + + + // Create outline font style + createOutline(font); + + // Set font data + _fonts[_loadedFonts++] = font; +} + +void Font::createOutline(FontData *font) { + int i; + int row; + int newByteWidth; + int oldByteWidth; + int newRowLength = 0; + size_t indexOffset = 0; + int index; + int currentByte; + unsigned char *basePointer; + unsigned char *srcPointer; + unsigned char *destPointer1; + unsigned char *destPointer2; + unsigned char *destPointer3; + unsigned char charRep; + + + // Populate new font style character data + for (i = 0; i < FONT_CHARCOUNT; i++) { + newByteWidth = 0; + oldByteWidth = 0; + index = font->normal.fontCharEntry[i].index; + if ((index > 0) || (i == FONT_FIRSTCHAR)) { + index += indexOffset; + } + + font->outline.fontCharEntry[i].index = index; + font->outline.fontCharEntry[i].tracking = font->normal.fontCharEntry[i].tracking; + font->outline.fontCharEntry[i].flag = font->normal.fontCharEntry[i].flag; + + if (font->normal.fontCharEntry[i].width != 0) { + newByteWidth = getByteLen(font->normal.fontCharEntry[i].width + 2); + oldByteWidth = getByteLen(font->normal.fontCharEntry[i].width); + + if (newByteWidth > oldByteWidth) { + indexOffset++; + } + } + + font->outline.fontCharEntry[i].width = font->normal.fontCharEntry[i].width + 2; + font->outline.fontCharEntry[i].byteWidth = newByteWidth; + newRowLength += newByteWidth; + } + + debug(2, "New row length: %d", newRowLength); + + font->outline.header = font->normal.header; + font->outline.header.charWidth += 2; + font->outline.header.charHeight += 2; + font->outline.header.rowLength = newRowLength; + + // Allocate new font representation storage + font->outline.font = (unsigned char *)calloc(newRowLength, font->outline.header.charHeight); + + + // Generate outline font representation + for (i = 0; i < FONT_CHARCOUNT; i++) { + for (row = 0; row < font->normal.header.charHeight; row++) { + for (currentByte = 0; currentByte < font->outline.fontCharEntry[i].byteWidth; currentByte++) { + basePointer = font->outline.font + font->outline.fontCharEntry[i].index + currentByte; + destPointer1 = basePointer + newRowLength * row; + destPointer2 = basePointer + newRowLength * (row + 1); + destPointer3 = basePointer + newRowLength * (row + 2); + if (currentByte > 0) { + // Get last two columns from previous byte + srcPointer = font->normal.font + font->normal.header.rowLength * row + font->normal.fontCharEntry[i].index + (currentByte - 1); + charRep = *srcPointer; + *destPointer1 |= ((charRep << 6) | (charRep << 7)); + *destPointer2 |= ((charRep << 6) | (charRep << 7)); + *destPointer3 |= ((charRep << 6) | (charRep << 7)); + } + + if (currentByte < font->normal.fontCharEntry[i].byteWidth) { + srcPointer = font->normal.font + font->normal.header.rowLength * row + font->normal.fontCharEntry[i].index + currentByte; + charRep = *srcPointer; + *destPointer1 |= charRep | (charRep >> 1) | (charRep >> 2); + *destPointer2 |= charRep | (charRep >> 1) | (charRep >> 2); + *destPointer3 |= charRep | (charRep >> 1) | (charRep >> 2); + } + } + } + + // "Hollow out" character to prevent overdraw + for (row = 0; row < font->normal.header.charHeight; row++) { + for (currentByte = 0; currentByte < font->outline.fontCharEntry[i].byteWidth; currentByte++) { + destPointer2 = font->outline.font + font->outline.header.rowLength * (row + 1) + font->outline.fontCharEntry[i].index + currentByte; + if (currentByte > 0) { + // Get last two columns from previous byte + srcPointer = font->normal.font + font->normal.header.rowLength * row + font->normal.fontCharEntry[i].index + (currentByte - 1); + *destPointer2 &= ((*srcPointer << 7) ^ 0xFFU); + } + + if (currentByte < font->normal.fontCharEntry[i].byteWidth) { + srcPointer = font->normal.font + font->normal.header.rowLength * row + font->normal.fontCharEntry[i].index + currentByte; + *destPointer2 &= ((*srcPointer >> 1) ^ 0xFFU); + } + } + } + } +} + +// Returns the horizontal length in pixels of the graphical representation +// of at most 'count' characters of the string 'text', taking +// into account any formatting options specified by 'flags'. +// If 'count' is 0, all characters of 'test' are counted. +int Font::getStringWidth(FontId fontId, const char *text, size_t count, FontEffectFlags flags) { + FontData *font; + size_t ct; + int width = 0; + int ch; + const byte *txt; + + + font = getFont(fontId); + + txt = (const byte *) text; + + for (ct = count; *txt && (!count || ct > 0); txt++, ct--) { + ch = *txt & 0xFFU; + // Translate character + ch = _charMap[ch]; + assert(ch < FONT_CHARCOUNT); + width += font->normal.fontCharEntry[ch].tracking; + } + + if ((flags & kFontBold) || (flags & kFontOutline)) { + width += 1; + } + + return width; +} + + +void Font::draw(FontId fontId, Surface *ds, const char *text, size_t count, const Common::Point &point, + int color, int effectColor, FontEffectFlags flags) { + FontData *font; + Point offsetPoint(point); + + font = getFont(fontId); + + if (flags & kFontOutline) { + offsetPoint.x--; + offsetPoint.y--; + outFont(font->outline, ds, text, count, offsetPoint, effectColor, flags); + outFont(font->normal, ds, text, count, point, color, flags); + } else if (flags & kFontShadow) { + offsetPoint.x--; + offsetPoint.y++; + outFont(font->normal, ds, text, count, offsetPoint, effectColor, flags); + outFont(font->normal, ds, text, count, point, color, flags); + } else { // FONT_NORMAL + outFont(font->normal, ds, text, count, point, color, flags); + } +} + +void Font::outFont(const FontStyle &drawFont, Surface *ds, const char *text, size_t count, const Common::Point &point, int color, FontEffectFlags flags) { + const byte *textPointer; + byte *c_dataPointer; + int c_code; + int charRow; + Point textPoint(point); + + byte *outputPointer; + byte *outputPointer_min; + byte *outputPointer_max; + + int row; + int rowLimit; + + int c_byte_len; + int c_byte; + int c_bit; + int ct; + + if ((point.x > ds->w) || (point.y > ds->h)) { + // Output string can't be visible + return; + } + + textPointer = (const byte *)text; + ct = count; + + // Draw string one character at a time, maximum of 'draw_str'_ct + // characters, or no limit if 'draw_str_ct' is 0 + for (; *textPointer && (!count || ct); textPointer++, ct--) { + c_code = *textPointer & 0xFFU; + + // Translate character + if (!(flags & kFontDontmap)) + c_code = _charMap[c_code]; + assert(c_code < FONT_CHARCOUNT); + + // Check if character is defined + if ((drawFont.fontCharEntry[c_code].index == 0) && (c_code != FONT_FIRSTCHAR)) { +#if FONT_SHOWUNDEFINED + if (c_code == FONT_CH_SPACE) { + textPoint.x += drawFont.fontCharEntry[c_code].tracking; + continue; + } + c_code = FONT_CH_QMARK; +#else + // Character code is not defined, but advance tracking + // ( Not defined if offset is 0, except for 33 ('!') which + // is defined ) + textPoint.x += drawFont.fontCharEntry[c_code].tracking; + continue; +#endif + } + + // Get length of character in bytes + c_byte_len = ((drawFont.fontCharEntry[c_code].width - 1) / 8) + 1; + rowLimit = (ds->h < (textPoint.y + drawFont.header.charHeight)) ? ds->h : textPoint.y + drawFont.header.charHeight; + charRow = 0; + + for (row = textPoint.y; row < rowLimit; row++, charRow++) { + // Clip negative rows */ + if (row < 0) { + continue; + } + + outputPointer = (byte *)ds->pixels + (ds->pitch * row) + textPoint.x; + outputPointer_min = (byte *)ds->pixels + (ds->pitch * row) + (textPoint.x > 0 ? textPoint.x : 0); + outputPointer_max = outputPointer + (ds->pitch - textPoint.x); + + // If character starts off the screen, jump to next character + if (outputPointer < outputPointer_min) { + break; + } + + c_dataPointer = drawFont.font + charRow * drawFont.header.rowLength + drawFont.fontCharEntry[c_code].index; + + for (c_byte = 0; c_byte < c_byte_len; c_byte++, c_dataPointer++) { + // Check each bit, draw pixel if bit is set + for (c_bit = 7; c_bit >= 0 && (outputPointer < outputPointer_max); c_bit--) { + if ((*c_dataPointer >> c_bit) & 0x01) { + *outputPointer = (byte)color; + } + outputPointer++; + } // end per-bit processing + } // end per-byte processing + } // end per-row processing + + // Advance tracking position + textPoint.x += drawFont.fontCharEntry[c_code].tracking; + } // end per-character processing +} + + +void Font::textDraw(FontId fontId, Surface *ds, const char *text, const Common::Point &point, int color, int effectColor, FontEffectFlags flags) { + int textWidth; + int textLength; + int fitWidth; + Common::Point textPoint(point); + + textLength = strlen(text); + + if (!(flags & kFontCentered)) { + // Text is not centered; No formatting required + draw(fontId, ds, text, textLength, point, color, effectColor, flags); + return; + } + + // Text is centered... format output + // Enforce minimum and maximum center points for centered text + if (textPoint.x < TEXT_CENTERLIMIT) { + textPoint.x = TEXT_CENTERLIMIT; + } + + if (textPoint.x > ds->w - TEXT_CENTERLIMIT) { + textPoint.x = ds->w - TEXT_CENTERLIMIT; + } + + if (textPoint.x < (TEXT_MARGIN * 2)) { + // Text can't be centered if it's too close to the margin + return; + } + + textWidth = getStringWidth(fontId, text, textLength, flags); + + if (textPoint.x < (ds->w / 2)) { + // Fit to right side + fitWidth = (textPoint.x - TEXT_MARGIN) * 2; + } else { + // Fit to left side + fitWidth = ((ds->w - TEXT_MARGIN) - textPoint.x) * 2; + } + + if (fitWidth < textWidth) { + warning("text too long to be displayed in one line"); + textWidth = fitWidth; + } + // Entire string fits, draw it + textPoint.x = textPoint.x - (textWidth / 2); + draw(fontId, ds, text, textLength, textPoint, color, effectColor, flags); +} + +int Font::getHeight(FontId fontId, const char *text, int width, FontEffectFlags flags) { + int textWidth; + int textLength; + int fitWidth; + const char *startPointer; + const char *searchPointer; + const char *measurePointer; + const char *foundPointer; + int len; + int w; + const char *endPointer; + int h; + int wc; + int w_total; + int len_total; + Common::Point textPoint; + Common::Point textPoint2; + + textLength = strlen(text); + + textWidth = getStringWidth(fontId, text, textLength, flags); + h = getHeight(fontId); + fitWidth = width; + + textPoint.x = (fitWidth / 2); + textPoint.y = 0; + + if (fitWidth >= textWidth) { + return h; + } + + // String won't fit on one line + w_total = 0; + len_total = 0; + wc = 0; + + startPointer = text; + measurePointer = text; + searchPointer = text; + endPointer = text + textLength; + + for (;;) { + foundPointer = strchr(searchPointer, ' '); + if (foundPointer == NULL) { + // Ran to the end of the buffer + len = endPointer - measurePointer; + } else { + len = foundPointer - measurePointer; + } + + w = getStringWidth(fontId, measurePointer, len, flags); + measurePointer = foundPointer; + + if ((w_total + w) > fitWidth) { + // This word won't fit + if (wc == 0) { + // The first word in the line didn't fit. Still print it + searchPointer = measurePointer + 1; + } + // Wrap what we've got and restart + textPoint.y += h + TEXT_LINESPACING; + if (foundPointer == NULL) { + // Since word hit NULL but fit, we are done + return textPoint.y + h; + } + w_total = 0; + len_total = 0; + wc = 0; + measurePointer = searchPointer; + startPointer = searchPointer; + } else { + // Word will fit ok + w_total += w; + len_total += len; + wc++; + if (foundPointer == NULL) { + // Since word hit NULL but fit, we are done + return textPoint.y + h; + } + searchPointer = measurePointer + 1; + } + } +} + +void Font::textDrawRect(FontId fontId, Surface *ds, const char *text, const Common::Rect &rect, int color, int effectColor, FontEffectFlags flags) { + int textWidth; + int textLength; + int fitWidth; + const char *startPointer; + const char *searchPointer; + const char *measurePointer; + const char *foundPointer; + int len; + int w; + const char *endPointer; + int h; + int wc; + int w_total; + int len_total; + Common::Point textPoint; + Common::Point textPoint2; + + textLength = strlen(text); + + textWidth = getStringWidth(fontId, text, textLength, flags); + fitWidth = rect.width(); + + textPoint.x = rect.left + (fitWidth / 2); + textPoint.y = rect.top; + + if (fitWidth >= textWidth) { + // Entire string fits, draw it + textPoint.x -= (textWidth / 2); + draw(fontId, ds, text, textLength, textPoint, color, effectColor, flags); + return; + } + + // String won't fit on one line + h = getHeight(fontId); + w_total = 0; + len_total = 0; + wc = 0; + + startPointer = text; + measurePointer = text; + searchPointer = text; + endPointer = text + textLength; + + for (;;) { + foundPointer = strchr(searchPointer, ' '); + if (foundPointer == NULL) { + // Ran to the end of the buffer + len = endPointer - measurePointer; + } else { + len = foundPointer - measurePointer; + } + + w = getStringWidth(fontId, measurePointer, len, flags); + measurePointer = foundPointer; + + if ((w_total + w) > fitWidth) { + // This word won't fit + if (wc == 0) { + w_total = fitWidth; + len_total = len; + } + + // Wrap what we've got and restart + textPoint2.x = textPoint.x - (w_total / 2); + textPoint2.y = textPoint.y; + draw(fontId, ds, startPointer, len_total, textPoint2, color, effectColor, flags); + textPoint.y += h + TEXT_LINESPACING; + if (textPoint.y >= rect.bottom) { + return; + } + w_total = 0; + len_total = 0; + if (wc == 0) { + searchPointer = measurePointer + 1; + } + wc = 0; + + // Advance the search pointer to the next non-space. + // Otherwise, the first "word" to be measured will be + // an empty string. Measuring or drawing a string of + // length 0 is interpreted as measure/draw the entire + // buffer, which certainly is not what we want here. + // + // This happes because a string may contain several + // spaces in a row, e.g. after a period. + + while (*searchPointer == ' ') + searchPointer++; + + measurePointer = searchPointer; + startPointer = searchPointer; + } else { + // Word will fit ok + w_total += w; + len_total += len; + wc++; + if (foundPointer == NULL) { + // Since word hit NULL but fit, we are done + textPoint2.x = textPoint.x - (w_total / 2); + textPoint2.y = textPoint.y; + draw(fontId, ds, startPointer, len_total, textPoint2, color, + effectColor, flags); + return; + } + searchPointer = measurePointer + 1; + } + } +} + +Font::FontId Font::knownFont2FontIdx(KnownFont font) { + FontId fontId = kSmallFont; + + if (_vm->getGameType() == GType_ITE) { + switch (font) + { + case (kKnownFontSmall): + fontId = kSmallFont; + break; + case (kKnownFontMedium): + fontId = kMediumFont; + break; + case (kKnownFontBig): + fontId = kBigFont; + break; + + case (kKnownFontVerb): + fontId = kSmallFont; + break; + case (kKnownFontScript): + fontId = kMediumFont; + break; + case (kKnownFontPause): + fontId = _vm->_font->valid(kBigFont) ? kBigFont : kMediumFont; + break; + } + } else if (_vm->getGameType() == GType_IHNM) { + switch (font) + { + case (kKnownFontSmall): + fontId = kSmallFont; + break; + case (kKnownFontMedium): + fontId = kMediumFont; + break; + case (kKnownFontBig): + fontId = kBigFont; + break; + + case (kKnownFontVerb): + fontId = kIHNMFont8; + break; + case (kKnownFontScript): + fontId = kIHNMMainFont; + break; + case (kKnownFontPause): + fontId = kMediumFont; // unchecked + break; + } + } + return fontId; +} + +} // End of namespace Saga |