mirror of
https://github.com/libretro/scummvm.git
synced 2025-02-13 07:14:59 +00:00
1208 lines
38 KiB
C++
1208 lines
38 KiB
C++
/* ScummVM - Graphic Adventure Engine
|
|
*
|
|
* ScummVM is the legal property of its developers, whose names
|
|
* are too numerous to list here. Please refer to the COPYRIGHT
|
|
* file distributed with this source distribution.
|
|
*
|
|
* 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 <http://www.gnu.org/licenses/>.
|
|
*
|
|
*/
|
|
|
|
// Font management and font drawing module
|
|
|
|
#include "saga/saga.h"
|
|
#include "saga/gfx.h"
|
|
#include "saga/resource.h"
|
|
#include "saga/scene.h"
|
|
#include "saga/font.h"
|
|
#include "saga/render.h"
|
|
|
|
#include "graphics/sjis.h"
|
|
#include "common/unicode-bidi.h"
|
|
|
|
#include "saga/ite8.h"
|
|
#include "saga/small8.h"
|
|
|
|
namespace Saga {
|
|
|
|
static const GameFontDescription ITEDEMO_GameFonts[] = { {0}, {1} };
|
|
static const GameFontDescription ITEWINDEMO_GameFonts[] = { {2}, {0} };
|
|
static const GameFontDescription ITE_GameFonts[] = { {2}, {0}, {1} };
|
|
static const GameFontDescription IHNMDEMO_GameFonts[] = { {2}, {3}, {4} };
|
|
// Font 6 is kIHNMFont8, font 8 is kIHNMMainFont
|
|
static const GameFontDescription IHNMCD_GameFonts[] = { {2}, {3}, {4}, {5}, {6}, {7}, {8} };
|
|
// Resource 2 is a CJK font. Resource 3 looks like some image. 4 to 8 are single-byte
|
|
// fonts (not really useful)
|
|
static const GameFontDescription IHNMZH_GameFonts[] = { {2}, {4}, {5}, {6}, {7}, {8} };
|
|
|
|
static struct {
|
|
const GameFontDescription *list;
|
|
int count;
|
|
} FontLists[FONTLIST_MAX] = {
|
|
/* FONTLIST_NONE */ { nullptr, 0 },
|
|
/* FONTLIST_ITE */ { ITE_GameFonts, ARRAYSIZE(ITE_GameFonts) },
|
|
/* FONTLIST_ITE_DEMO */ { ITEDEMO_GameFonts, ARRAYSIZE(ITEDEMO_GameFonts) },
|
|
/* FONTLIST_ITE_WIN_DEMO */ { ITEWINDEMO_GameFonts, ARRAYSIZE(ITEWINDEMO_GameFonts) },
|
|
/* FONTLIST_IHNM_DEMO */ { IHNMDEMO_GameFonts, ARRAYSIZE(IHNMDEMO_GameFonts) },
|
|
/* FONTLIST_IHNM_CD */ { IHNMCD_GameFonts, ARRAYSIZE(IHNMCD_GameFonts) },
|
|
/* FONTLIST_IHNM_ZH */ { IHNMZH_GameFonts, ARRAYSIZE(IHNMZH_GameFonts) },
|
|
};
|
|
|
|
Font::FontId Font::knownFont2FontIdx(KnownFont font) {
|
|
FontId fontId = kSmallFont;
|
|
|
|
// 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;
|
|
|
|
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->getLanguage() == Common::ZH_TWN) {
|
|
// There is only one Chinese font in Chinese version AFAICT.
|
|
// And very little non-Chinese characters to care about them
|
|
fontId = kSmallFont;
|
|
} 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
|
|
}
|
|
return fontId;
|
|
}
|
|
|
|
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 = getStringLength(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);
|
|
}
|
|
|
|
DefaultFont::DefaultFont(SagaEngine *vm) : Font(vm), _fontMapping(0), _chineseFont(nullptr), _cjkFontWidth(0), _cjkFontHeight(0), _koreanFont(nullptr) {
|
|
int i;
|
|
|
|
// Load font module resource context
|
|
|
|
GameFontList index = _vm->getFontList();
|
|
assert(index < FONTLIST_MAX && index >= FONTLIST_NONE);
|
|
assert(FontLists[index].list || FontLists[index].count == 0);
|
|
assert(FontLists[index].count > 0 || (_vm->getFeatures() & GF_EMBED_FONT));
|
|
|
|
_fonts.resize(MAX<int>(FontLists[index].count, (_vm->getFeatures() & GF_EMBED_FONT) ? 2 : 0));
|
|
|
|
for (i = 0; i < FontLists[index].count; i++) {
|
|
#ifdef __DS__
|
|
_fonts[i].outline.font = NULL;
|
|
_fonts[i].normal.font = NULL;
|
|
#endif
|
|
if (i == 0 && index == FONTLIST_IHNM_ZH)
|
|
loadChineseFontIHNM(&_fonts[i], FontLists[index].list[i].fontResourceId);
|
|
else
|
|
loadFont(&_fonts[i], FontLists[index].list[i].fontResourceId);
|
|
}
|
|
|
|
if (_vm->getFeatures() & GF_EMBED_FONT) {
|
|
loadFont(&_fonts[kSmallFont], ByteArray(font_small8, sizeof(font_small8)), true);
|
|
loadFont(&_fonts[kMediumFont], ByteArray(font_ite8, sizeof(font_ite8)), true);
|
|
}
|
|
|
|
if (_vm->getGameId() == GID_ITE && _vm->getLanguage() == Common::ZH_TWN)
|
|
loadChineseFontITE("ite.fnt");
|
|
|
|
if (_vm->getGameId() == GID_IHNM && _vm->getLanguage() == Common::KO_KOR)
|
|
loadKoreanFontIHNM("sbh1616.fnt");
|
|
}
|
|
|
|
DefaultFont::~DefaultFont() {
|
|
debug(8, "DefaultFont::~DefaultFont(): Freeing fonts.");
|
|
|
|
#ifdef __DS__
|
|
for (uint i = 0; i < _fonts.size(); i++) {
|
|
if (_fonts[i].outline.font) {
|
|
free(_fonts[i].outline.font);
|
|
}
|
|
|
|
if (_fonts[i].normal.font) {
|
|
free(_fonts[i].normal.font);
|
|
}
|
|
}
|
|
#endif
|
|
if (_chineseFont) {
|
|
delete[] _chineseFont;
|
|
_chineseFont = nullptr;
|
|
}
|
|
|
|
if (_koreanFont) {
|
|
delete[] _koreanFont;
|
|
_koreanFont = nullptr;
|
|
}
|
|
}
|
|
|
|
void DefaultFont::loadChineseFontITE(const Common::Path &fileName) {
|
|
Common::File f;
|
|
if (!f.open(fileName))
|
|
return;
|
|
_cjkFontWidth = 16;
|
|
_cjkFontHeight = 14;
|
|
_chineseFontIndex = Common::move(Common::Array<int>(0x8000, -1));
|
|
size_t sz = f.size();
|
|
_chineseFont = new byte[sz];
|
|
f.read(_chineseFont, sz);
|
|
static const int kGlyphSize = 30;
|
|
for (unsigned i = 0; i < sz / kGlyphSize; i++) {
|
|
uint16 ch = READ_BE_UINT16(_chineseFont + kGlyphSize * i);
|
|
if (!(ch & 0x8000))
|
|
continue;
|
|
_chineseFontIndex[ch&0x7fff] = kGlyphSize * i + 2;
|
|
}
|
|
}
|
|
|
|
void DefaultFont::loadKoreanFontIHNM(const Common::Path &fileName) {
|
|
Common::File f;
|
|
if (!f.open(fileName))
|
|
return;
|
|
size_t sz = f.size();
|
|
if (sz < kIHNMKoreanNonJamoOffset * kIHNMKoreanGlyphBytes)
|
|
return;
|
|
|
|
_cjkFontWidth = 16;
|
|
_cjkFontHeight = 16;
|
|
|
|
_koreanFont = new byte[sz];
|
|
f.read(_koreanFont, sz);
|
|
}
|
|
|
|
|
|
void DefaultFont::saveBig5Index(byte head, byte tail, uint curIdx) {
|
|
_chineseFontIndex[((head & 0x7f) << 8) | tail] = curIdx;
|
|
}
|
|
|
|
void DefaultFont::loadChineseFontIHNM(FontData *font, uint32 fontResourceId) {
|
|
ByteArray fontResourceData;
|
|
int c;
|
|
ResourceContext *fontContext;
|
|
|
|
debug(1, "Font::loadChineseFontIHNM(): Reading fontResourceId %d...", fontResourceId);
|
|
|
|
fontContext = _vm->_resource->getContext(GAME_RESOURCEFILE);
|
|
if (fontContext == nullptr) {
|
|
error("DefaultFont::Font() resource context not found");
|
|
}
|
|
|
|
// Load font resource
|
|
_vm->_resource->loadResource(fontContext, fontResourceId, fontResourceData);
|
|
|
|
ByteArrayReadStreamEndian readS(fontResourceData, fontContext->isBigEndian());
|
|
|
|
// Read font header
|
|
font->normal.header.charHeight = 15;
|
|
font->normal.header.charWidth = 8;
|
|
font->normal.header.rowLength = 1;
|
|
|
|
for (c = 0; c < FONT_CHARCOUNT; c++) {
|
|
font->normal.fontCharEntry[c].width = 8;
|
|
font->normal.fontCharEntry[c].byteWidth = 1;
|
|
font->normal.fontCharEntry[c].flag = 0;
|
|
font->normal.fontCharEntry[c].tracking = 8;
|
|
}
|
|
|
|
_chineseFont = new byte[fontResourceData.size()];
|
|
memcpy(_chineseFont, fontResourceData.getBuffer(), fontResourceData.size());
|
|
_cjkFontWidth = 16;
|
|
_cjkFontHeight = 15;
|
|
_chineseFontIndex = Common::move(Common::Array<int>(0x8000, -1));
|
|
|
|
// No idea what is the beginning, some 3 values and then some bitmask,
|
|
// anyway file is constant and as long as we know how to interpret
|
|
// and match glyphs we're good
|
|
int curIdx = 1286;
|
|
|
|
// It's just sequential big5 codepoints by compartments specified in
|
|
// Big5 specification. Compartments are out of order
|
|
// 0x8140 to 0xA0FE Reserved for user-defined characters 造字
|
|
// Not present
|
|
// 0xA440 to 0xC67E Frequently used characters 常用字
|
|
for (byte head = 0xa4; head <= 0xc5; head++) {
|
|
for (byte tail = 0x40; tail <= 0x7e; tail++, curIdx += 30)
|
|
saveBig5Index(head, tail, curIdx);
|
|
for (byte tail = 0xa1; tail <= 0xfe; tail++, curIdx += 30)
|
|
saveBig5Index(head, tail, curIdx);
|
|
}
|
|
|
|
for (byte tail = 0x40; tail <= 0x7e; tail++, curIdx += 30)
|
|
saveBig5Index(0xc6, tail, curIdx);
|
|
|
|
// 0xC6A1 to 0xC8FE Reserved for user-defined characters.
|
|
// Not present
|
|
|
|
// 0xC940 to 0xF9D5 Less frequently used characters 次常用字
|
|
// Rounded up to F9FE with pseudographics characters
|
|
for (byte head = 0xc9; head <= 0xf9; head++) {
|
|
for (byte tail = 0x40; tail <= 0x7e; tail++, curIdx += 30)
|
|
saveBig5Index(head, tail, curIdx);
|
|
for (byte tail = 0xa1; tail <= 0xfe; tail++, curIdx += 30)
|
|
saveBig5Index(head, tail, curIdx);
|
|
}
|
|
|
|
// 0xFA40 to 0xFEFE Reserved for user-defined characters
|
|
// Not present
|
|
|
|
// Then comes back to a140
|
|
// 0xA140 to 0xA3BF "Graphical characters" 圖形碼
|
|
for (byte head = 0xa1; head <= 0xa2; head++) {
|
|
for (byte tail = 0x40; tail <= 0x7e; tail++, curIdx += 30)
|
|
saveBig5Index(head, tail, curIdx);
|
|
for (byte tail = 0xa1; tail <= 0xfe; tail++, curIdx += 30)
|
|
saveBig5Index(head, tail, curIdx);
|
|
}
|
|
|
|
for (byte tail = 0x40; tail <= 0x7e; tail++, curIdx += 30)
|
|
saveBig5Index(0xa3, tail, curIdx);
|
|
for (byte tail = 0xa1; tail <= 0xbf; tail++, curIdx += 30)
|
|
saveBig5Index(0xa3, tail, curIdx);
|
|
|
|
// 0xA3C0 to 0xA3FE Reserved, not for user-defined characters
|
|
// Not present
|
|
|
|
// Then single-width ASCII
|
|
int startASCII = curIdx;
|
|
|
|
for (c = 0; c < FONT_CHARCOUNT; c++, curIdx += 15) {
|
|
font->normal.fontCharEntry[c].index = curIdx - startASCII;
|
|
}
|
|
|
|
#ifndef __DS__
|
|
font->normal.font.resize(fontResourceData.size() - startASCII);
|
|
memcpy(font->normal.font.getBuffer(), fontResourceData.getBuffer() + startASCII, fontResourceData.size() - startASCII);
|
|
#else
|
|
if (font->normal.font) {
|
|
free(font->normal.font);
|
|
}
|
|
|
|
font->normal.font = (byte *)malloc(fontResourceData.size() - startASCII);
|
|
memcpy(font->normal.font, fontResourceData.getBuffer() + startASCII, fontResourceData.size() - startASCII);
|
|
#endif
|
|
|
|
// Create outline font style
|
|
createOutline(font);
|
|
}
|
|
|
|
void DefaultFont::textDrawRect(FontId fontId, 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;
|
|
Common::Point textPoint;
|
|
Common::Point textPoint2;
|
|
|
|
textLength = getStringLength(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, text, textLength, textPoint, color, effectColor, flags);
|
|
return;
|
|
}
|
|
|
|
// String won't fit on one line
|
|
h = getHeight(fontId, text);
|
|
w_total = 0;
|
|
wc = 0;
|
|
|
|
startPointer = text;
|
|
measurePointer = text;
|
|
searchPointer = text;
|
|
endPointer = text + textLength;
|
|
|
|
// IHNM korean uses spaces, so we use western algorithm for it.
|
|
bool isBig5 = !!_chineseFont;
|
|
|
|
for (;;) {
|
|
if (isBig5) {
|
|
if ((searchPointer[0] & 0x80) && searchPointer[1])
|
|
foundPointer = searchPointer + 2;
|
|
else if (*searchPointer)
|
|
foundPointer = searchPointer + 1;
|
|
else
|
|
foundPointer = nullptr;
|
|
} else
|
|
foundPointer = strchr(searchPointer, ' ');
|
|
if (foundPointer == nullptr) {
|
|
// 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) {
|
|
if (measurePointer)
|
|
searchPointer = measurePointer;
|
|
else
|
|
searchPointer = endPointer;
|
|
w_total = fitWidth;
|
|
}
|
|
|
|
// Wrap what we've got and restart
|
|
textPoint2.x = textPoint.x - (w_total / 2);
|
|
textPoint2.y = textPoint.y;
|
|
draw(fontId, startPointer, searchPointer - startPointer, textPoint2, color, effectColor, flags);
|
|
textPoint.y += h + TEXT_LINESPACING;
|
|
if (textPoint.y >= rect.bottom) {
|
|
return;
|
|
}
|
|
w_total = 0;
|
|
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;
|
|
wc++;
|
|
if (foundPointer == nullptr) {
|
|
// Since word hit NULL but fit, we are done
|
|
textPoint2.x = textPoint.x - (w_total / 2);
|
|
textPoint2.y = textPoint.y;
|
|
draw(fontId, startPointer, endPointer - startPointer, textPoint2, color,
|
|
effectColor, flags);
|
|
return;
|
|
}
|
|
if (isBig5 && (*measurePointer & 0x80))
|
|
searchPointer = measurePointer + 2;
|
|
else
|
|
searchPointer = measurePointer + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
int DefaultFont::translateChar(int charId) {
|
|
if (charId <= 127 || (_vm->getLanguage() == Common::RU_RUS && charId <= 255) || (_vm->getLanguage() == Common::HE_ISR && charId <= 255))
|
|
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;
|
|
bool isCJK = _chineseFont || _koreanFont;
|
|
|
|
for (ct = count; *txt && (!count || ct > 0); txt++, ct--) {
|
|
ch = *txt & 0xFFU;
|
|
if ((ch & 0x80) && isCJK) {
|
|
byte trailing = *++txt & 0xFFU;
|
|
ct--;
|
|
if (ct == 0 || trailing == 0)
|
|
break;
|
|
width += _cjkFontWidth;
|
|
continue;
|
|
}
|
|
|
|
// Translate character
|
|
ch = translateChar(ch);
|
|
assert(ch < FONT_CHARCOUNT);
|
|
width += font->normal.fontCharEntry[ch].tracking;
|
|
}
|
|
|
|
if ((flags & kFontBold) || (flags & kFontOutline)) {
|
|
width += 1;
|
|
}
|
|
|
|
return width;
|
|
}
|
|
|
|
int DefaultFont::getHeight(FontId fontId, const char *text, int width, FontEffectFlags flags) {
|
|
int textWidth;
|
|
int textLength;
|
|
int fitWidth;
|
|
const char *searchPointer;
|
|
const char *measurePointer;
|
|
const char *foundPointer;
|
|
int len;
|
|
int w;
|
|
const char *endPointer;
|
|
int h;
|
|
int wc;
|
|
int w_total;
|
|
Common::Point textPoint;
|
|
|
|
textLength = getStringLength(text);
|
|
textWidth = getStringWidth(fontId, text, textLength, flags);
|
|
h = getHeight(fontId, text);
|
|
fitWidth = width;
|
|
|
|
textPoint.x = (fitWidth / 2);
|
|
textPoint.y = 0;
|
|
|
|
if (fitWidth >= textWidth) {
|
|
return h;
|
|
}
|
|
|
|
// String won't fit on one line
|
|
w_total = 0;
|
|
wc = 0;
|
|
|
|
measurePointer = text;
|
|
searchPointer = text;
|
|
endPointer = text + textLength;
|
|
|
|
// IHNM korean uses spaces, so we use western algorithm for it.
|
|
bool isBig5 = !!_chineseFont;
|
|
|
|
for (;;) {
|
|
if (isBig5) {
|
|
if (*searchPointer & 0x80)
|
|
foundPointer = searchPointer + 2;
|
|
else if (*searchPointer)
|
|
foundPointer = searchPointer + 1;
|
|
else
|
|
foundPointer = nullptr;
|
|
} else
|
|
foundPointer = strchr(searchPointer, ' ');
|
|
if (foundPointer == nullptr) {
|
|
// 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
|
|
if (isBig5 && (*measurePointer & 0x80))
|
|
searchPointer = measurePointer + 2;
|
|
else
|
|
searchPointer = measurePointer + 1;
|
|
}
|
|
// Wrap what we've got and restart
|
|
textPoint.y += h + TEXT_LINESPACING;
|
|
if (foundPointer == nullptr) {
|
|
// Since word hit NULL but fit, we are done
|
|
return textPoint.y + h;
|
|
}
|
|
w_total = 0;
|
|
wc = 0;
|
|
measurePointer = searchPointer;
|
|
} else {
|
|
// Word will fit ok
|
|
w_total += w;
|
|
wc++;
|
|
if (foundPointer == nullptr) {
|
|
// Since word hit NULL but fit, we are done
|
|
return textPoint.y + h;
|
|
}
|
|
if (isBig5 && (*measurePointer & 0x80))
|
|
searchPointer = measurePointer + 2;
|
|
else
|
|
searchPointer = measurePointer + 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
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 (_vm->getLanguage() == Common::HE_ISR) {
|
|
Common::String textstr(text, count);
|
|
text = Common::convertBiDiString(textstr, Common::kWindows1255).c_str();
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|
|
|
|
int DefaultFont::getHeight(FontId fontId, const char *text) {
|
|
int singleByteHeight = getHeight(fontId);
|
|
if ((!_chineseFont && !_koreanFont) || _cjkFontHeight < singleByteHeight)
|
|
return singleByteHeight;
|
|
|
|
for (const byte *textPointer = (const byte *)text; *textPointer; textPointer++)
|
|
if (*textPointer & 0x80)
|
|
return _cjkFontHeight;
|
|
|
|
return singleByteHeight;
|
|
}
|
|
|
|
void DefaultFont::blitGlyph(const Common::Point &textPoint, const byte* bitmap, int charWidth, int charHeight, int rowLength, byte color) {
|
|
// Get length of character in bytes
|
|
int c_byte_len = ((charWidth - 1) / 8) + 1;
|
|
int rowLimit = (_vm->_gfx->getBackBufferHeight() < (textPoint.y + charHeight)) ? _vm->_gfx->getBackBufferHeight() : textPoint.y + charHeight;
|
|
int charRow = 0;
|
|
|
|
for (int row = textPoint.y; row < rowLimit; row++, charRow++) {
|
|
// Clip negative rows */
|
|
if (row < 0) {
|
|
continue;
|
|
}
|
|
|
|
byte *outputPointer = _vm->_gfx->getBackBufferPixels() + (_vm->_gfx->getBackBufferPitch() * row) + textPoint.x;
|
|
byte *outputPointer_min = _vm->_gfx->getBackBufferPixels() + (_vm->_gfx->getBackBufferPitch() * row) + (textPoint.x > 0 ? textPoint.x : 0);
|
|
byte *outputPointer_max = outputPointer + (_vm->_gfx->getBackBufferPitch() - textPoint.x);
|
|
|
|
// If character starts off the screen, jump to next character
|
|
if (outputPointer < outputPointer_min) {
|
|
break;
|
|
}
|
|
|
|
const byte *c_dataPointer = bitmap + charRow * rowLength;
|
|
|
|
for (int c_byte = 0; c_byte < c_byte_len; c_byte++, c_dataPointer++) {
|
|
// Check each bit, draw pixel if bit is set
|
|
for (int 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
|
|
}
|
|
|
|
void DefaultFont::outFont(const FontStyle &drawFont, const char *text, size_t count, const Common::Point &point, int color, FontEffectFlags flags) {
|
|
const byte *textPointer;
|
|
int c_code;
|
|
Point textPoint(point);
|
|
|
|
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;
|
|
|
|
bool isBig5 = !!_chineseFont;
|
|
bool isJohab = !!_koreanFont;
|
|
|
|
// 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;
|
|
|
|
if ((c_code & 0x80) && isJohab) {
|
|
byte leading = c_code;
|
|
byte trailing = *++textPointer & 0xFFU;
|
|
ct--;
|
|
if (ct == 0 || trailing == 0)
|
|
break;
|
|
uint16 full = ((leading & 0x7f) << 8) | trailing;
|
|
int initial = (full >> 10) & 0x1f;
|
|
int mid = (full >> 5) & 0x1f;
|
|
int fin = full & 0x1f;
|
|
int initidx = initial - 1;
|
|
static const int mididxlut[0x20] = {
|
|
-1, -1, 0, 1, 2, 3, 4, 5, -1, -1, 6, 7, 8, 9,
|
|
10, 11, -1, -1, 12, 13, 14, 15, 16, 17, -1, -1,
|
|
18, 19, 20, 21, -1, -1};
|
|
int mididx = mididxlut[mid];
|
|
int finidx = fin >= 0x12 ? fin - 2 : fin - 1;
|
|
|
|
// Validate character
|
|
if (initial >= 0x15 || initidx < 0 || mididx < 0 || fin == 0 || fin == 0x12 || fin >= 0x1e) {
|
|
// Characters with initial over 0x15 means "non-jamo-based", e.g. Hanja, pictograms
|
|
// and so on. They are present in the font but not supported by renderer neither in
|
|
// the original nor in the scummvm
|
|
textPoint.x += _cjkFontWidth;
|
|
continue;
|
|
}
|
|
|
|
static const int mid2inivariant[2][32] = {
|
|
// Special case: empty final
|
|
{
|
|
0, 0, 0, 0, 0, 0, 0, 0,
|
|
0, 0, 0, 0, 0, 1, 3, 3,
|
|
0, 0, 3, 1, 2, 4, 4, 4,
|
|
0, 0, 2, 1, 3, 0, 0, 0,
|
|
},
|
|
// Otherwise we have a final
|
|
{
|
|
0, 0, 0, 5, 5, 5, 5, 5,
|
|
0, 0, 5, 5, 5, 6, 7, 7,
|
|
0, 0, 7, 6, 6, 7, 7, 7,
|
|
0, 0, 6, 6, 7, 5, 0, 0,
|
|
}
|
|
};
|
|
int inivariant = mid2inivariant[fin != 1][mid];
|
|
int midvariant = 0;
|
|
|
|
if (fin != 1)
|
|
midvariant += 2;
|
|
if (initial == 2 || initial == 17)
|
|
midvariant++;
|
|
|
|
static const int mid2finvariant[32] = {
|
|
0, 0, 0, 0, 2, 0, 2, 1,
|
|
0, 0, 2, 1, 2, 3, 0, 2,
|
|
0, 0, 1, 3, 3, 1, 2, 1,
|
|
0, 0, 3, 3, 1, 1, 0, 0,
|
|
};
|
|
int finvariant = mid2finvariant[mid];
|
|
|
|
int initialoff = kIHNMKoreanGlyphBytes * (initidx + inivariant * kIHNMKoreanInitials);
|
|
blitGlyph(textPoint, _koreanFont + initialoff, _cjkFontWidth, _cjkFontHeight, _cjkFontWidth / 8, (byte)color);
|
|
int midoff = kIHNMKoreanGlyphBytes * (mididx + midvariant * kIHNMKoreanMids + kIHNMKoreanMidOffset);
|
|
blitGlyph(textPoint, _koreanFont + midoff, _cjkFontWidth, _cjkFontHeight, _cjkFontWidth / 8, (byte)color);
|
|
int finoff = kIHNMKoreanGlyphBytes * (finidx + finvariant * kIHNMKoreanFinals + kIHNMKoreanFinalsOffset);
|
|
blitGlyph(textPoint, _koreanFont + finoff, _cjkFontWidth, _cjkFontHeight, _cjkFontWidth / 8, (byte)color);
|
|
|
|
// Advance tracking position
|
|
textPoint.x += _cjkFontWidth;
|
|
continue;
|
|
}
|
|
|
|
|
|
if ((c_code & 0x80) && isBig5) {
|
|
byte leading = c_code;
|
|
byte trailing = *++textPointer & 0xFFU;
|
|
ct--;
|
|
if (ct == 0 || trailing == 0)
|
|
break;
|
|
int idx = _chineseFontIndex[((leading & 0x7f) << 8) | trailing];
|
|
if (idx < 0) {
|
|
textPoint.x += _cjkFontWidth;
|
|
continue;
|
|
}
|
|
blitGlyph(textPoint, _chineseFont + idx, _cjkFontWidth, _cjkFontHeight, _cjkFontWidth / 8, (byte)color);
|
|
// Advance tracking position
|
|
textPoint.x += _cjkFontWidth;
|
|
continue;
|
|
}
|
|
|
|
// 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 #3405: "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
|
|
}
|
|
|
|
blitGlyph(textPoint, &drawFont.font[drawFont.fontCharEntry[c_code].index], drawFont.fontCharEntry[c_code].width, drawFont.header.charHeight,
|
|
drawFont.header.rowLength, (byte)color);
|
|
|
|
// Advance tracking position
|
|
textPoint.x += drawFont.fontCharEntry[c_code].tracking;
|
|
} // end per-character processing
|
|
|
|
int 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;
|
|
ResourceContext *fontContext;
|
|
|
|
debug(1, "Font::loadFont(): Reading fontResourceId %d...", fontResourceId);
|
|
|
|
fontContext = _vm->_resource->getContext(GAME_RESOURCEFILE);
|
|
if (fontContext == nullptr) {
|
|
error("DefaultFont::Font() resource context not found");
|
|
}
|
|
|
|
// Load font resource
|
|
_vm->_resource->loadResource(fontContext, fontResourceId, fontResourceData);
|
|
|
|
loadFont(font, fontResourceData, fontContext->isBigEndian());
|
|
}
|
|
|
|
void DefaultFont::loadFont(FontData *font, const ByteArray& fontResourceData, bool isBigEndian) {
|
|
int numBits;
|
|
int c;
|
|
|
|
if (fontResourceData.size() < FONT_DESCSIZE) {
|
|
error("DefaultFont::loadFont() Invalid font length (%i < %i)", (int)fontResourceData.size(), FONT_DESCSIZE);
|
|
}
|
|
|
|
ByteArrayReadStreamEndian readS(fontResourceData, 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(nullptr) {
|
|
_font = Graphics::FontSJIS::createFont(vm->getPlatform());
|
|
assert(_font);
|
|
}
|
|
|
|
SJISFont::~SJISFont() {
|
|
delete _font;
|
|
}
|
|
|
|
void SJISFont::textDrawRect(FontId fontId, const char *text, const Common::Rect &rect, int color, int effectColor, FontEffectFlags flags) {
|
|
Common::Point textPoint(rect.left, rect.top);
|
|
int curW = 0;
|
|
int numChar = 0;
|
|
const char *pos = text;
|
|
const char *last = nullptr;
|
|
int checkWidth = (rect.width() - 16) & ~7;
|
|
|
|
for (uint16 c = fetchChar(pos); c; c = fetchChar(pos)) {
|
|
curW += (_font->getCharWidth(c) >> 1);
|
|
if ((curW > checkWidth && !preventLineBreakForCharacter(c)) || c == (uint16)'\r' || c == (uint16)'\n') {
|
|
draw(fontId, text, numChar, textPoint, color, effectColor, flags);
|
|
numChar = 0;
|
|
textPoint.x = rect.left;
|
|
textPoint.y += (getHeight(fontId));
|
|
// Abort if there is no more space inside the rect
|
|
if (textPoint.y + getHeight(fontId) > rect.bottom)
|
|
return;
|
|
// Skip linebreak characters
|
|
if (c == (uint16)'\r' || c == (uint16)'\n')
|
|
last++;
|
|
pos = text = last;
|
|
last = nullptr;
|
|
curW = 0;
|
|
} else {
|
|
numChar++;
|
|
last = pos;
|
|
}
|
|
}
|
|
|
|
// If the whole string fits into one line it gets aligned to the center
|
|
if (textPoint.y == rect.top)
|
|
textPoint.x = textPoint.x + (rect.width() - getStringWidth(fontId, text, 0, flags)) / 2;
|
|
|
|
draw(fontId, text, numChar, textPoint, color, effectColor, flags);
|
|
}
|
|
|
|
int SJISFont::getStringLength(const char *text) {
|
|
int res = 0;
|
|
while (fetchChar(text))
|
|
res++;
|
|
|
|
return res;
|
|
}
|
|
|
|
int SJISFont::getStringWidth(FontId fontId, const char *text, size_t count, FontEffectFlags flags) {
|
|
// The spacing is always the same regardless of the fontId and font style
|
|
_font->setDrawingMode(Graphics::FontSJIS::kDefaultMode);
|
|
int curW = 0;
|
|
int maxW = 0;
|
|
|
|
for (uint16 c = fetchChar(text); c; c = fetchChar(text)) {
|
|
if (c == (uint16)'\r' || c == (uint16)'\n') {
|
|
maxW = MAX<int>(curW, maxW);
|
|
curW = 0;
|
|
continue;
|
|
}
|
|
curW += _font->getCharWidth(c);
|
|
if (!--count)
|
|
break;
|
|
}
|
|
|
|
return MAX<int>(curW, maxW) >> 1;
|
|
}
|
|
|
|
int SJISFont::getHeight(FontId fontId, const char *text, int width, FontEffectFlags flags) {
|
|
Graphics::FontSJIS::DrawingMode mode = Graphics::FontSJIS::kDefaultMode;
|
|
if (flags & kFontOutline)
|
|
mode = Graphics::FontSJIS::kOutlineMode;
|
|
else if (flags & kFontShadow)
|
|
mode = Graphics::FontSJIS::kShadowRightMode;
|
|
|
|
_font->setDrawingMode(mode);
|
|
int res = _font->getFontHeight();
|
|
int checkWidth = (width - 16) & ~7;
|
|
int tmpWidth = 0;
|
|
|
|
for (uint16 c = fetchChar(text); c; c = fetchChar(text)) {
|
|
// The spacing is always the same (regardless of the fontId and font style) for the char spacing, but not for the line spacing.
|
|
_font->setDrawingMode(Graphics::FontSJIS::kDefaultMode);
|
|
tmpWidth += (_font->getCharWidth(c) >> 1);
|
|
if ((tmpWidth > checkWidth && !preventLineBreakForCharacter(c)) || c == (uint16)'\r' || c == (uint16)'\n') {
|
|
tmpWidth = tmpWidth > width ? _font->getCharWidth(c) >> 1 : 0;
|
|
_font->setDrawingMode(mode);
|
|
res += _font->getFontHeight();
|
|
}
|
|
}
|
|
|
|
return (res + 1) >> 1;
|
|
}
|
|
|
|
int SJISFont::getHeight(FontId fontId) {
|
|
// The spacing here is always the same regardless of the style
|
|
_font->setDrawingMode(Graphics::FontSJIS::kDefaultMode);
|
|
return (_font->getFontHeight() >> 1) + 1;
|
|
}
|
|
|
|
void SJISFont::draw(FontId fontId, const char *text, size_t count, const Common::Point &point, int color, int effectColor, FontEffectFlags flags) {
|
|
int16 x = point.x << 1;
|
|
int16 y = point.y << 1;
|
|
|
|
Graphics::FontSJIS::DrawingMode mode = Graphics::FontSJIS::kDefaultMode;
|
|
if (effectColor != 0x80) {
|
|
if (flags & kFontOutline)
|
|
mode = Graphics::FontSJIS::kOutlineMode;
|
|
else if (flags & kFontShadow)
|
|
mode = Graphics::FontSJIS::kShadowRightMode;
|
|
}
|
|
|
|
// DEBUG: The Graphics::FontSJIS code currently does not allow glyphs to be outlined and shaded at the same time. I'll implement it if I have to, but currently I don't think that this is the case...
|
|
assert((flags & 3) != 3);
|
|
_font->setDrawingMode(mode);
|
|
Common::Rect dirtyRect((flags & kFontShadow) ? MAX<int16>(point.x - 1, 0) : point.x, point.y, point.x + 1, point.y + (_font->getFontHeight() >> 1));
|
|
|
|
while (*text) {
|
|
uint16 ch = fetchChar(text);
|
|
_font->setDrawingMode(mode);
|
|
if (ch == (uint16)'\r' || ch == (uint16)'\n') {
|
|
dirtyRect.right = MAX<int16>(x >> 1, dirtyRect.right);
|
|
y += _font->getFontHeight();
|
|
x = point.x << 1;
|
|
continue;
|
|
}
|
|
_font->drawChar(_vm->_gfx->getSJISBackBuffer(), ch, x, y, color, effectColor);
|
|
// Reset drawing mode for the shadow mode extra drawing and for the character spacing (not line spacing)
|
|
_font->setDrawingMode(Graphics::FontSJIS::kDefaultMode);
|
|
if (flags & kFontShadow)
|
|
_font->drawChar(_vm->_gfx->getSJISBackBuffer(), ch, MAX<int16>(x - 1, 0), y, color, 0);
|
|
x += _font->getCharWidth(ch);
|
|
if (!--count)
|
|
break;
|
|
}
|
|
|
|
dirtyRect.right = MAX<int16>(x >> 1, dirtyRect.right);
|
|
dirtyRect.bottom = (y + _font->getFontHeight()) >> 1;
|
|
_vm->_render->addDirtyRect(dirtyRect);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
bool SJISFont::preventLineBreakForCharacter(uint16 ch) const {
|
|
uint8 c = (ch >> 8) & 0xFF;
|
|
return c && ((c >= 0x81 && c <= 0x9F) || c >= 0xE0);
|
|
}
|
|
|
|
} // End of namespace Saga
|