SAGA: (ITE/PC98) - prepare font class for Japanese rendering

Reorganize font code into sub classes to allow independent rendering of the Japanese ROM font. No actual implementation for the Japanese rendering yet.
This commit is contained in:
athrxx 2020-08-02 18:30:41 +02:00
parent b1972dea57
commit fa671a2c40
4 changed files with 584 additions and 510 deletions

View File

@ -29,434 +29,64 @@
#include "saga/font.h"
#include "saga/render.h"
#include "graphics/sjis.h"
namespace Saga {
Font::Font(SagaEngine *vm) : _vm(vm) {
int i;
Font::FontId Font::knownFont2FontIdx(KnownFont font) {
FontId fontId = kSmallFont;
// Load font module resource context
// The demo version of IHNM has 3 font types (like ITE), not 6 (like the full version of IHNM)
if (_vm->getGameId() == GID_ITE || _vm->isIHNMDemo()) {
switch (font) {
case (kKnownFontSmall):
default:
fontId = kSmallFont;
break;
case (kKnownFontMedium):
fontId = kMediumFont;
break;
case (kKnownFontBig):
fontId = kBigFont;
break;
assert(_vm->getFontsCount() > 0);
case (kKnownFontVerb):
fontId = kSmallFont;
break;
case (kKnownFontScript):
fontId = kMediumFont;
break;
case (kKnownFontPause):
fontId = _vm->_font->valid(kBigFont) ? kBigFont : kMediumFont;
break;
}
#ifdef ENABLE_IHNM
} else if (_vm->getGameId() == GID_IHNM && !_vm->isIHNMDemo()) {
switch (font) {
case (kKnownFontSmall):
default:
fontId = kSmallFont;
break;
case (kKnownFontMedium):
fontId = kMediumFont;
break;
case (kKnownFontBig):
fontId = kBigFont;
break;
_fonts.resize(_vm->getFontsCount());
for (i = 0; i < _vm->getFontsCount(); i++) {
#ifdef __DS__
_fonts[i].outline.font = NULL;
_fonts[i].normal.font = NULL;
case (kKnownFontVerb):
fontId = kIHNMFont8;
break;
case (kKnownFontScript):
fontId = kIHNMMainFont;
break;
case (kKnownFontPause):
fontId = kMediumFont; // unchecked
break;
}
#endif
loadFont(&_fonts[i], _vm->getFontDescription(i)->fontResourceId);
}
_fontMapping = 0;
}
Font::~Font() {
debug(8, "Font::~Font(): Freeing fonts.");
#ifdef __DS__
for (int i = 0; i < _vm->getFontsCount(); i++) {
if (_fonts[i].outline.font) {
free(_fonts[i].outline.font);
}
if (_fonts[i].normal.font) {
free(_fonts[i].normal.font);
}
}
#endif
}
void Font::loadFont(FontData *font, uint32 fontResourceId) {
ByteArray fontResourceData;
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, fontResourceData);
if (fontResourceData.size() < FONT_DESCSIZE) {
error("Font::loadFont() Invalid font length (%i < %i)", (int)fontResourceData.size(), FONT_DESCSIZE);
}
ByteArrayReadStreamEndian readS(fontResourceData, fontContext->isBigEndian());
// 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");
}
#ifndef __DS__
font->normal.font.resize(fontResourceData.size() - FONT_DESCSIZE);
memcpy(font->normal.font.getBuffer(), fontResourceData.getBuffer() + FONT_DESCSIZE, fontResourceData.size() - FONT_DESCSIZE);
#else
if (font->normal.font) {
free(font->normal.font);
}
font->normal.font = (byte *) malloc(fontResourceData.size() - FONT_DESCSIZE);
memcpy(font->normal.font, fontResourceData.getBuffer() + FONT_DESCSIZE, fontResourceData.size() - FONT_DESCSIZE);
#endif
// Create outline font style
createOutline(font);
}
void Font::createOutline(FontData *font) {
int i;
int row;
int newByteWidth;
int newRowLength = 0;
int currentByte;
byte *basePointer;
byte *srcPointer;
byte *destPointer1;
byte *destPointer2;
byte *destPointer3;
byte charRep;
// Populate new font style character data
for (i = 0; i < FONT_CHARCOUNT; i++) {
newByteWidth = 0;
font->outline.fontCharEntry[i].index = newRowLength;
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);
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
#ifdef __DS__
if (font->outline.font) {
free(font->outline.font);
}
font->outline.font = (byte *) calloc(newRowLength * font->outline.header.charHeight, 1);
#else
font->outline.font.resize(newRowLength * font->outline.header.charHeight);
#endif
// 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);
}
}
}
}
}
int Font::translateChar(int charId) {
if (charId <= 127 || (_vm->getLanguage() == Common::RU_RUS && charId <= 254))
return charId; // normal character
else
return _charMap[charId - 128]; // extended character
}
// 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 = translateChar(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, 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, text, count, offsetPoint, effectColor, flags);
outFont(font->normal, text, count, point, color, flags);
} else if (flags & kFontShadow) {
offsetPoint.x--;
offsetPoint.y++;
outFont(font->normal, text, count, offsetPoint, effectColor, flags);
outFont(font->normal, text, count, point, color, flags);
} else { // FONT_NORMAL
outFont(font->normal, text, count, point, color, flags);
}
}
void Font::outFont(const FontStyle &drawFont, const char *text, size_t count, const Common::Point &point, int color, FontEffectFlags flags) {
const byte *textPointer;
const byte *c_dataPointer;
int c_code;
int charRow = 0;
Point textPoint(point);
byte *outputPointer;
byte *outputPointer_min;
byte *outputPointer_max;
int row = 0;
int rowLimit = 0;
int c_byte_len;
int c_byte;
int c_bit;
int ct;
if ((point.x > _vm->_gfx->getBackBufferWidth()) || (point.y > _vm->_gfx->getBackBufferHeight())) {
// 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 (_fontMapping == 0) { // Check font mapping debug flag
// Default game behavior
// It seems that this font mapping causes problems with non-english
// versions of IHNM, so it has been changed to apply for ITE only.
// It doesn't make any difference for the English version of IHNM.
// Fixes bug #1796045: "IHNM: Spanish font wrong".
if (!(flags & kFontDontmap) && _vm->getGameId() == GID_ITE) {
if (_vm->getLanguage() != Common::IT_ITA) {
c_code = translateChar(c_code);
} else {
// The in-game fonts of the Italian version should not be mapped.
// The ones in the intro are hardcoded and should be mapped normally.
if (_vm->_scene->isInIntro())
c_code = translateChar(c_code);
}
}
} else if (_fontMapping == 1) {
// Force font mapping
c_code = translateChar(c_code);
} else {
// In all other cases, ignore font mapping
}
assert(c_code < FONT_CHARCOUNT);
// Check if character is defined
if ((drawFont.fontCharEntry[c_code].index == 0) && (c_code != FONT_FIRSTCHAR)) {
#if FONT_SHOWUNDEFINED
// A tab character appears in the IHNM demo instructions screen, so filter
// it out here
if (c_code == FONT_CH_SPACE || c_code == FONT_CH_TAB) {
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 = (_vm->_gfx->getBackBufferHeight() < (textPoint.y + drawFont.header.charHeight)) ? _vm->_gfx->getBackBufferHeight() : textPoint.y + drawFont.header.charHeight;
charRow = 0;
for (row = textPoint.y; row < rowLimit; row++, charRow++) {
// Clip negative rows */
if (row < 0) {
continue;
}
outputPointer = _vm->_gfx->getBackBufferPixels() + (_vm->_gfx->getBackBufferPitch() * row) + textPoint.x;
outputPointer_min = _vm->_gfx->getBackBufferPixels() + (_vm->_gfx->getBackBufferPitch() * row) + (textPoint.x > 0 ? textPoint.x : 0);
outputPointer_max = outputPointer + (_vm->_gfx->getBackBufferPitch() - 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
rowLimit = (_vm->_gfx->getBackBufferHeight() < (textPoint.y + drawFont.header.charHeight)) ? _vm->_gfx->getBackBufferHeight() : textPoint.y + drawFont.header.charHeight;
_vm->_render->addDirtyRect(Common::Rect(point.x, point.y, textPoint.x, rowLimit));
}
void Font::textDraw(FontId fontId, 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, 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 > _vm->_gfx->getBackBufferWidth() - TEXT_CENTERLIMIT) {
textPoint.x = _vm->_gfx->getBackBufferWidth() - 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 < (_vm->_gfx->getBackBufferWidth() / 2)) {
// Fit to right side
fitWidth = (textPoint.x - TEXT_MARGIN) * 2;
} else {
// Fit to left side
fitWidth = ((_vm->_gfx->getBackBufferWidth() - 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, text, textLength, textPoint, color, effectColor, flags);
return fontId;
}
int Font::getHeight(FontId fontId, const char *text, int width, FontEffectFlags flags) {
@ -649,60 +279,456 @@ void Font::textDrawRect(FontId fontId, const char *text, const Common::Rect &rec
}
}
Font::FontId Font::knownFont2FontIdx(KnownFont font) {
FontId fontId = kSmallFont;
void Font::textDraw(FontId fontId, const char *text, const Common::Point &point, int color, int effectColor, FontEffectFlags flags) {
int textWidth;
int textLength;
int fitWidth;
Common::Point textPoint(point);
// The demo version of IHNM has 3 font types (like ITE), not 6 (like the full version of IHNM)
if (_vm->getGameId() == GID_ITE || _vm->isIHNMDemo()) {
switch (font) {
case (kKnownFontSmall):
default:
fontId = kSmallFont;
break;
case (kKnownFontMedium):
fontId = kMediumFont;
break;
case (kKnownFontBig):
fontId = kBigFont;
break;
textLength = strlen(text);
case (kKnownFontVerb):
fontId = kSmallFont;
break;
case (kKnownFontScript):
fontId = kMediumFont;
break;
case (kKnownFontPause):
fontId = _vm->_font->valid(kBigFont) ? kBigFont : kMediumFont;
break;
}
#ifdef ENABLE_IHNM
} else if (_vm->getGameId() == GID_IHNM && !_vm->isIHNMDemo()) {
switch (font) {
case (kKnownFontSmall):
default:
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;
}
#endif
if (!(flags & kFontCentered)) {
// Text is not centered; No formatting required
draw(fontId, text, textLength, point, color, effectColor, flags);
return;
}
return fontId;
// 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 > _vm->_gfx->getBackBufferWidth() - TEXT_CENTERLIMIT) {
textPoint.x = _vm->_gfx->getBackBufferWidth() - 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 < (_vm->_gfx->getBackBufferWidth() / 2)) {
// Fit to right side
fitWidth = (textPoint.x - TEXT_MARGIN) * 2;
} else {
// Fit to left side
fitWidth = ((_vm->_gfx->getBackBufferWidth() - 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, text, textLength, textPoint, color, effectColor, flags);
}
DefaultFont::DefaultFont(SagaEngine *vm) : Font(vm), _fontMapping(0) {
int i;
// Load font module resource context
assert(_vm->getFontsCount() > 0);
_fonts.resize(_vm->getFontsCount());
for (i = 0; i < _vm->getFontsCount(); i++) {
#ifdef __DS__
_fonts[i].outline.font = NULL;
_fonts[i].normal.font = NULL;
#endif
loadFont(&_fonts[i], _vm->getFontDescription(i)->fontResourceId);
}
}
DefaultFont::~DefaultFont() {
debug(8, "DefaultFont::~DefaultFont(): Freeing fonts.");
#ifdef __DS__
for (int i = 0; i < _vm->getFontsCount(); i++) {
if (_fonts[i].outline.font) {
free(_fonts[i].outline.font);
}
if (_fonts[i].normal.font) {
free(_fonts[i].normal.font);
}
}
#endif
}
int DefaultFont::translateChar(int charId) {
if (charId <= 127 || (_vm->getLanguage() == Common::RU_RUS && charId <= 254))
return charId; // normal character
else
return _charMap[charId - 128]; // extended character
}
// 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 DefaultFont::getStringWidth(FontId fontId, const char *text, size_t count, FontEffectFlags flags) {
size_t ct;
int width = 0;
int ch;
const byte *txt;
FontData *font = getFont(fontId);
txt = (const byte *) text;
for (ct = count; *txt && (!count || ct > 0); txt++, ct--) {
ch = *txt & 0xFFU;
// Translate character
ch = translateChar(ch);
assert(ch < FONT_CHARCOUNT);
width += font->normal.fontCharEntry[ch].tracking;
}
if ((flags & kFontBold) || (flags & kFontOutline)) {
width += 1;
}
return width;
}
void DefaultFont::draw(FontId fontId, const char *text, size_t count, const Common::Point &point,
int color, int effectColor, FontEffectFlags flags) {
Point offsetPoint(point);
FontData *font = getFont(fontId);
if (flags & kFontOutline) {
offsetPoint.x--;
offsetPoint.y--;
outFont(font->outline, text, count, offsetPoint, effectColor, flags);
outFont(font->normal, text, count, point, color, flags);
} else if (flags & kFontShadow) {
offsetPoint.x--;
offsetPoint.y++;
outFont(font->normal, text, count, offsetPoint, effectColor, flags);
outFont(font->normal, text, count, point, color, flags);
} else { // FONT_NORMAL
outFont(font->normal, text, count, point, color, flags);
}
}
void DefaultFont::outFont(const FontStyle &drawFont, const char *text, size_t count, const Common::Point &point, int color, FontEffectFlags flags) {
const byte *textPointer;
const byte *c_dataPointer;
int c_code;
int charRow = 0;
Point textPoint(point);
byte *outputPointer;
byte *outputPointer_min;
byte *outputPointer_max;
int row = 0;
int rowLimit = 0;
int c_byte_len;
int c_byte;
int c_bit;
int ct;
if ((point.x > _vm->_gfx->getBackBufferWidth()) || (point.y > _vm->_gfx->getBackBufferHeight())) {
// 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 (_fontMapping == 0) { // Check font mapping debug flag
// Default game behavior
// It seems that this font mapping causes problems with non-english
// versions of IHNM, so it has been changed to apply for ITE only.
// It doesn't make any difference for the English version of IHNM.
// Fixes bug #1796045: "IHNM: Spanish font wrong".
if (!(flags & kFontDontmap) && _vm->getGameId() == GID_ITE) {
if (_vm->getLanguage() != Common::IT_ITA) {
c_code = translateChar(c_code);
} else {
// The in-game fonts of the Italian version should not be mapped.
// The ones in the intro are hardcoded and should be mapped normally.
if (_vm->_scene->isInIntro())
c_code = translateChar(c_code);
}
}
} else if (_fontMapping == 1) {
// Force font mapping
c_code = translateChar(c_code);
} else {
// In all other cases, ignore font mapping
}
assert(c_code < FONT_CHARCOUNT);
// Check if character is defined
if ((drawFont.fontCharEntry[c_code].index == 0) && (c_code != FONT_FIRSTCHAR)) {
#if FONT_SHOWUNDEFINED
// A tab character appears in the IHNM demo instructions screen, so filter
// it out here
if (c_code == FONT_CH_SPACE || c_code == FONT_CH_TAB) {
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 = (_vm->_gfx->getBackBufferHeight() < (textPoint.y + drawFont.header.charHeight)) ? _vm->_gfx->getBackBufferHeight() : textPoint.y + drawFont.header.charHeight;
charRow = 0;
for (row = textPoint.y; row < rowLimit; row++, charRow++) {
// Clip negative rows */
if (row < 0) {
continue;
}
outputPointer = _vm->_gfx->getBackBufferPixels() + (_vm->_gfx->getBackBufferPitch() * row) + textPoint.x;
outputPointer_min = _vm->_gfx->getBackBufferPixels() + (_vm->_gfx->getBackBufferPitch() * row) + (textPoint.x > 0 ? textPoint.x : 0);
outputPointer_max = outputPointer + (_vm->_gfx->getBackBufferPitch() - 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
rowLimit = (_vm->_gfx->getBackBufferHeight() < (textPoint.y + drawFont.header.charHeight)) ? _vm->_gfx->getBackBufferHeight() : textPoint.y + drawFont.header.charHeight;
_vm->_render->addDirtyRect(Common::Rect(point.x, point.y, textPoint.x, rowLimit));
}
void DefaultFont::loadFont(FontData *font, uint32 fontResourceId) {
ByteArray fontResourceData;
int numBits;
int c;
ResourceContext *fontContext;
debug(1, "Font::loadFont(): Reading fontResourceId %d...", fontResourceId);
fontContext = _vm->_resource->getContext(GAME_RESOURCEFILE);
if (fontContext == NULL) {
error("DefaultFont::Font() resource context not found");
}
// Load font resource
_vm->_resource->loadResource(fontContext, fontResourceId, fontResourceData);
if (fontResourceData.size() < FONT_DESCSIZE) {
error("DefaultFont::loadFont() Invalid font length (%i < %i)", (int)fontResourceData.size(), FONT_DESCSIZE);
}
ByteArrayReadStreamEndian readS(fontResourceData, fontContext->isBigEndian());
// 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");
}
#ifndef __DS__
font->normal.font.resize(fontResourceData.size() - FONT_DESCSIZE);
memcpy(font->normal.font.getBuffer(), fontResourceData.getBuffer() + FONT_DESCSIZE, fontResourceData.size() - FONT_DESCSIZE);
#else
if (font->normal.font) {
free(font->normal.font);
}
font->normal.font = (byte *)malloc(fontResourceData.size() - FONT_DESCSIZE);
memcpy(font->normal.font, fontResourceData.getBuffer() + FONT_DESCSIZE, fontResourceData.size() - FONT_DESCSIZE);
#endif
// Create outline font style
createOutline(font);
}
void DefaultFont::createOutline(FontData *font) {
int i;
int row;
int newByteWidth;
int newRowLength = 0;
int currentByte;
byte *basePointer;
byte *srcPointer;
byte *destPointer1;
byte *destPointer2;
byte *destPointer3;
byte charRep;
// Populate new font style character data
for (i = 0; i < FONT_CHARCOUNT; i++) {
newByteWidth = 0;
font->outline.fontCharEntry[i].index = newRowLength;
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);
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
#ifdef __DS__
if (font->outline.font) {
free(font->outline.font);
}
font->outline.font = (byte *)calloc(newRowLength * font->outline.header.charHeight, 1);
#else
font->outline.font.resize(newRowLength * font->outline.header.charHeight);
#endif
// 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);
}
}
}
}
}
SJISFont::SJISFont(SagaEngine *vm) : Font(vm), _font(0) {
_font = Graphics::FontSJIS::createFont(vm->getPlatform());
assert(_font);
}
SJISFont::~SJISFont() {
delete _font;
}
int SJISFont::getStringWidth(FontId fontId, const char *text, size_t count, FontEffectFlags flags) {
int res = 0;
for (uint16 c = fetchChar(text); c; c = fetchChar(text))
res += _font->getCharWidth(c);
return res;
}
int SJISFont::getHeight(FontId fontId) {
return _font->getFontHeight();
}
void SJISFont::draw(FontId fontId, const char *text, size_t count, const Common::Point &point, int color, int effectColor, FontEffectFlags flags) {
}
uint16 SJISFont::fetchChar(const char *&s) const {
uint16 ch = (uint8)*s++;
if (ch <= 0x7F || (ch >= 0xA1 && ch <= 0xDF))
return ch;
ch |= (uint8)(*s++) << 8;
return ch;
}
} // End of namespace Saga

View File

@ -28,6 +28,10 @@
#include "common/list.h"
#include "saga/gfx.h"
namespace Graphics {
class FontSJIS;
}
namespace Saga {
#define FONT_SHOWUNDEFINED 1 // Define to draw undefined characters * as ?'s
@ -139,58 +143,71 @@ struct FontData {
};
class Font {
public:
Font(SagaEngine *vm);
~Font();
public:
Font(SagaEngine *vm) : _vm(vm) {}
virtual ~Font() {}
int getStringWidth(KnownFont font, const char *text, size_t count, FontEffectFlags flags) {
return getStringWidth(knownFont2FontIdx(font), text, count, flags);
}
int getHeight(KnownFont font) {
return getHeight(knownFont2FontIdx(font));
}
int getHeight(KnownFont font, const char *text, int width, FontEffectFlags flags) {
return getHeight(knownFont2FontIdx(font), text, width, flags);
}
void textDraw(KnownFont font, const char *string, const Common::Point &point, int color, int effectColor, FontEffectFlags flags) {
textDraw(knownFont2FontIdx(font), string, point, color, effectColor, flags);
}
void textDrawRect(KnownFont font, const char *text, const Common::Rect &rect, int color, int effectColor, FontEffectFlags flags) {
textDrawRect(knownFont2FontIdx(font), text, rect, color, effectColor, flags);
}
virtual void setFontMapping(int) {}
protected:
enum FontId {
kSmallFont,
kMediumFont,
kBigFont,
kIHNMUnknown,
kIHNMFont8,
kIHNMUnknown2,
kIHNMMainFont
};
SagaEngine *_vm;
private:
FontId knownFont2FontIdx(KnownFont font);
int getHeight(FontId fontId, const char *text, int width, FontEffectFlags flags);
void textDrawRect(FontId fontId, const char *text, const Common::Rect &rect, int color, int effectColor, FontEffectFlags flags);
void textDraw(FontId fontId, const char *string, const Common::Point &point, int color, int effectColor, FontEffectFlags flags);
virtual int translateChar(int charId) = 0;
virtual int getStringWidth(FontId fontId, const char *text, size_t count, FontEffectFlags flags) = 0;
virtual int getHeight(FontId fontId) = 0;
virtual bool valid(FontId) = 0;
virtual void draw(FontId fontId, const char *text, size_t count, const Common::Point &point, int color, int effectColor, FontEffectFlags flags) = 0;
};
class DefaultFont : public Font {
public:
DefaultFont(SagaEngine *vm);
~DefaultFont() override;
void setFontMapping(int mapping) {
_fontMapping = mapping;
}
private:
enum FontId {
kSmallFont,
kMediumFont,
kBigFont,
kIHNMUnknown,
kIHNMFont8,
kIHNMUnknown2,
kIHNMMainFont
};
Font::FontId knownFont2FontIdx(KnownFont font);
int translateChar(int charId);
int getStringWidth(FontId fontId, const char *text, size_t count, FontEffectFlags flags);
int getHeight(FontId fontId, const char *text, int width, FontEffectFlags flags);
void textDrawRect(FontId fontId, const char *text, const Common::Rect &rect, int color, int effectColor, FontEffectFlags flags);
void textDraw(FontId fontId, const char *string, const Common::Point &point, int color, int effectColor, FontEffectFlags flags);
void loadFont(FontData *font, uint32 fontResourceId);
void createOutline(FontData *font);
void draw(FontId fontId, const char *text, size_t count, const Common::Point &point, int color, int effectColor, FontEffectFlags flags);
void outFont(const FontStyle &drawFont, const char *text, size_t count, const Common::Point &point, int color, FontEffectFlags flags);
FontData *getFont(FontId fontId) {
validate(fontId);
return &_fonts[fontId];
}
int getHeight(FontId fontId) {
int translateChar(int charId) override;
int getStringWidth(FontId fontId, const char *text, size_t count, FontEffectFlags flags) override;
int getHeight(FontId fontId) override {
return getFont(fontId)->normal.header.charHeight;
}
@ -199,9 +216,21 @@ class Font {
error("Font::validate: Invalid font id");
}
}
bool valid(FontId fontId) {
bool valid(FontId fontId) override {
return (uint(fontId) < _fonts.size());
}
FontData *getFont(FontId fontId) {
validate(fontId);
return &_fonts[fontId];
}
void draw(FontId fontId, const char *text, size_t count, const Common::Point &point, int color, int effectColor, FontEffectFlags flags) override;
void outFont(const FontStyle &drawFont, const char *text, size_t count, const Common::Point &point, int color, FontEffectFlags flags);
void loadFont(FontData *font, uint32 fontResourceId);
void createOutline(FontData *font);
int getByteLen(int numBits) const {
int byteLength = numBits / 8;
@ -213,11 +242,27 @@ class Font {
}
static const int _charMap[128];
SagaEngine *_vm;
int _fontMapping;
Common::Array<FontData> _fonts;
int _fontMapping;
};
class SJISFont : public Font {
public:
SJISFont(SagaEngine *vm);
~SJISFont() override;
private:
int translateChar(int charId) override { return charId; }
int getStringWidth(FontId fontId, const char *text, size_t count, FontEffectFlags flags) override;
int getHeight(FontId fontId) override;
bool valid(FontId fontId) override { return fontId != kBigFont; }
void draw(FontId fontId, const char *text, size_t count, const Common::Point &point, int color, int effectColor, FontEffectFlags flags) override;
uint16 fetchChar(const char *&s) const;
Graphics::FontSJIS *_font;
};
} // End of namespace Saga

View File

@ -29,7 +29,7 @@
namespace Saga {
const int Font::_charMap[128] = {
const int DefaultFont::_charMap[128] = {
// Characters 0 - 127 are mapped directly to ISO 8859-1
199, // 128 LATIN CAPITAL LETTER C WITH CEDILLA
252, // 129 LATIN SMALL LETTER U WITH DIAERESIS

View File

@ -254,7 +254,10 @@ Common::Error SagaEngine::run() {
_events = new Events(this);
if (!isSaga2()) {
_font = new Font(this);
if (getLanguage() == Common::JA_JPN)
_font = new SJISFont(this);
else
_font = new DefaultFont(this);
_sprite = new Sprite(this);
_script = new SAGA1Script(this);
} else {