400 lines
11 KiB
C++
400 lines
11 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/>.
|
|
*
|
|
*/
|
|
|
|
#include "scumm/he/font_he.h"
|
|
#include "scumm/he/intern_he.h"
|
|
#include "scumm/he/wiz_he.h"
|
|
|
|
#include "common/archive.h"
|
|
#include "common/compression/unzip.h"
|
|
#include "common/config-manager.h"
|
|
#include "common/debug.h"
|
|
#include "common/fs.h"
|
|
|
|
#include "graphics/surface.h"
|
|
|
|
#ifdef USE_FREETYPE2
|
|
#include "graphics/fonts/ttf.h"
|
|
#endif
|
|
|
|
namespace Scumm {
|
|
|
|
HEFont::HEFont(ScummEngine_v99he *vm) : _vm(vm), _fontsEnumerated(false) {
|
|
_fontContextList.size();
|
|
}
|
|
|
|
HEFont::~HEFont() {
|
|
for (Common::List<HEFontContextElement *>::iterator it = _fontContextList.begin();
|
|
it != _fontContextList.end(); ++it) {
|
|
delete *it;
|
|
}
|
|
|
|
_fontContextList.clear();
|
|
_fontEntries.clear();
|
|
}
|
|
|
|
HEFontContextElement *HEFont::findFontContext(int imageNum) {
|
|
for (Common::List<HEFontContextElement *>::iterator it = _fontContextList.begin();
|
|
it != _fontContextList.end(); ++it) {
|
|
|
|
if ((*it)->imageNumber == imageNum) {
|
|
return *it;
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
bool HEFont::startFont(int imageNum) {
|
|
// Check if a context already exists for this image...
|
|
HEFontContextElement *existing = findFontContext(imageNum);
|
|
if (existing) {
|
|
debug(1, "HEFont::startFont(): Warning - font system exists for image %d. Destroying existing.", imageNum);
|
|
endFont(imageNum);
|
|
}
|
|
|
|
// Verify the image is suitable...
|
|
WizSimpleBitmap bitmap;
|
|
if (!_vm->_wiz->dwSetSimpleBitmapStructFromImage(imageNum, 0, &bitmap)) {
|
|
debug(1, "HEFont::startFont(): Image %d is not suitable for rendering (must not be compressed)", imageNum);
|
|
return false;
|
|
}
|
|
|
|
// Create new font context...
|
|
HEFontContextElement *newContext = new HEFontContextElement();
|
|
newContext->imageNumber = imageNum;
|
|
|
|
_fontContextList.push_back(newContext);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HEFont::endFont(int imageNum) {
|
|
for (Common::List<HEFontContextElement *>::iterator it = _fontContextList.begin();
|
|
it != _fontContextList.end(); ++it) {
|
|
|
|
if ((*it)->imageNumber == imageNum) {
|
|
delete *it;
|
|
_fontContextList.erase(it);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
debug(1, "HEFont::endFont(): Font system not started for image %d", imageNum);
|
|
return false;
|
|
}
|
|
|
|
bool HEFont::createFont(int imageNum, const char *fontName, int fgColor, int bgColor, int style, int size) {
|
|
HEFontContextElement *ctx = findFontContext(imageNum);
|
|
if (!ctx) {
|
|
debug(1, "HEFont::createFont(): Font system not created for image %d", imageNum);
|
|
return false;
|
|
}
|
|
|
|
// Destroy any previously created font...
|
|
if (ctx->font) {
|
|
delete ctx->font;
|
|
ctx->font = nullptr;
|
|
}
|
|
|
|
#ifdef USE_FREETYPE2
|
|
// Some of the models really want to use Arial, for some reason...
|
|
if (Common::String(fontName).equalsIgnoreCase("Arial")) {
|
|
fontName = "LiberationSans-Regular";
|
|
}
|
|
|
|
// Find the filename for this font name...
|
|
Common::String fontFileName;
|
|
for (uint i = 0; i < _fontEntries.size(); i++) {
|
|
if (_fontEntries[i].fontName.equalsIgnoreCase(fontName)) {
|
|
fontFileName = _fontEntries[i].fileName + ".ttf";
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Fallback: try using fontName directly as filename...
|
|
if (fontFileName.empty()) {
|
|
fontFileName = Common::String(fontName) + ".ttf";
|
|
}
|
|
|
|
// Try loading from fonts.dat...
|
|
ctx->font = Graphics::loadTTFFontFromArchive(fontFileName,
|
|
size,
|
|
Graphics::kTTFSizeModeCell,
|
|
0, 0,
|
|
Graphics::kTTFRenderModeLight,
|
|
nullptr);
|
|
|
|
// If not in archive, we're probably searching for a game-specific font, so try via SearchMan...
|
|
if (!ctx->font) {
|
|
Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(Common::Path(fontFileName));
|
|
|
|
if (stream) {
|
|
ctx->font = Graphics::loadTTFFont(stream,
|
|
DisposeAfterUse::YES,
|
|
size,
|
|
Graphics::kTTFSizeModeCell,
|
|
0, 0,
|
|
Graphics::kTTFRenderModeLight,
|
|
nullptr);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
if (!ctx->font) {
|
|
debug(1, "HEFont::createFont(): Could not create font '%s'", fontName);
|
|
return false;
|
|
}
|
|
|
|
// Store colors
|
|
ctx->fgColor = fgColor;
|
|
ctx->bgColor = bgColor;
|
|
ctx->transparentBg = (bgColor == _vm->VAR(_vm->VAR_WIZ_TRANSPARENT_COLOR));
|
|
|
|
// Set alignment
|
|
ctx->align = Graphics::kTextAlignCenter; // Default
|
|
if (style & kHEFontStyleLeft) {
|
|
ctx->align = Graphics::kTextAlignLeft;
|
|
} else if (style & kHEFontStyleRight) {
|
|
ctx->align = Graphics::kTextAlignRight;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool HEFont::renderString(int imageNum, int imageState, int xPos, int yPos, const char *string) {
|
|
HEFontContextElement *ctx = findFontContext(imageNum);
|
|
if (!ctx) {
|
|
debug(1, "HEFont::renderString(): Font system not created for image %d", imageNum);
|
|
return false;
|
|
}
|
|
|
|
if (!ctx->font) {
|
|
debug(1, "HEFont::renderString(): No font selected for image %d", imageNum);
|
|
return false;
|
|
}
|
|
|
|
// Get the bitmap from the current image...
|
|
WizSimpleBitmap bitmap;
|
|
if (!_vm->_wiz->dwSetSimpleBitmapStructFromImage(imageNum, imageState, &bitmap)) {
|
|
debug(1, "HEFont::renderString(): Could not get bitmap from image %d", imageNum);
|
|
return false;
|
|
}
|
|
|
|
// Wrap it in a Surface, which we can assume is a 8-bit paletted one for our use case...
|
|
Graphics::Surface surface;
|
|
surface.init(bitmap.bitmapWidth, bitmap.bitmapHeight,
|
|
bitmap.bitmapWidth,
|
|
bitmap.bufferPtr(),
|
|
Graphics::PixelFormat::createFormatCLUT8());
|
|
|
|
int stringWidth = ctx->font->getStringWidth(string);
|
|
int stringHeight = ctx->font->getFontHeight();
|
|
|
|
// If background is not transparent, fill the background rectangle first...
|
|
if (!ctx->transparentBg) {
|
|
Common::Rect bgRect(xPos, yPos, xPos + stringWidth, yPos + stringHeight);
|
|
surface.fillRect(bgRect, ctx->bgColor);
|
|
}
|
|
|
|
// Draw the string!
|
|
ctx->font->drawString(&surface, string, xPos, yPos, stringWidth, ctx->fgColor, ctx->align);
|
|
|
|
return true;
|
|
}
|
|
|
|
int HEFont::getStringWidth(int imageNum, const char *string) {
|
|
HEFontContextElement *ctx = findFontContext(imageNum);
|
|
if (!ctx) {
|
|
debug(1, "HEFont::getStringWidth(): Font system not created for image %d", imageNum);
|
|
return 0;
|
|
}
|
|
|
|
if (!ctx->font) {
|
|
debug(1, "HEFont::getStringWidth(): No font selected for image %d", imageNum);
|
|
return 0;
|
|
}
|
|
|
|
return ctx->font->getStringWidth(string);
|
|
}
|
|
|
|
int HEFont::getStringHeight(int imageNum, const char *string) {
|
|
HEFontContextElement *ctx = findFontContext(imageNum);
|
|
if (!ctx) {
|
|
debug(1, "HEFont::getStringHeight(): Font system not created for image %d", imageNum);
|
|
return 0;
|
|
}
|
|
|
|
if (!ctx->font) {
|
|
debug(1, "HEFont::getStringHeight(): No font selected for image %d", imageNum);
|
|
return 0;
|
|
}
|
|
|
|
return ctx->font->getFontHeight();
|
|
}
|
|
|
|
void HEFont::enumerateFonts() {
|
|
if (_fontsEnumerated) {
|
|
return;
|
|
}
|
|
|
|
_fontEntries.clear();
|
|
|
|
#ifdef USE_FREETYPE2
|
|
Common::Array<Common::String> candidateFonts;
|
|
|
|
// Open fonts.dat...
|
|
Common::SeekableReadStream *archiveStream = nullptr;
|
|
|
|
if (ConfMan.hasKey("extrapath")) {
|
|
Common::FSDirectory extrapath(ConfMan.getPath("extrapath"));
|
|
archiveStream = extrapath.createReadStreamForMember("fonts.dat");
|
|
}
|
|
|
|
if (!archiveStream) {
|
|
archiveStream = SearchMan.createReadStreamForMember("fonts.dat");
|
|
}
|
|
|
|
if (archiveStream) {
|
|
Common::Archive *archive = Common::makeZipArchive(archiveStream);
|
|
if (archive) {
|
|
Common::ArchiveMemberList members;
|
|
archive->listMembers(members);
|
|
|
|
for (Common::ArchiveMemberList::const_iterator it = members.begin();
|
|
it != members.end(); ++it) {
|
|
Common::String name = (*it)->getName();
|
|
if (name.hasSuffixIgnoreCase(".ttf")) {
|
|
Common::String fontName = name.substr(0, name.size() - 4);
|
|
candidateFonts.push_back(fontName);
|
|
}
|
|
}
|
|
|
|
delete archive;
|
|
}
|
|
}
|
|
|
|
// If not in archive, we're probably searching for a game-specific font, so try via SearchMan...
|
|
Common::ArchiveMemberList fontFiles;
|
|
SearchMan.listMatchingMembers(fontFiles, "*.ttf");
|
|
|
|
// Use filenames for fonts.dat fonts...
|
|
for (uint i = 0; i < candidateFonts.size(); i++) {
|
|
Common::String fontFileName = candidateFonts[i] + ".ttf";
|
|
|
|
Graphics::Font *testFont = Graphics::loadTTFFontFromArchive(
|
|
fontFileName,
|
|
12,
|
|
Graphics::kTTFSizeModeCharacter
|
|
);
|
|
|
|
if (testFont) {
|
|
HEFontEntry entry;
|
|
entry.fileName = candidateFonts[i];
|
|
entry.fontName = candidateFonts[i];
|
|
_fontEntries.push_back(entry);
|
|
delete testFont;
|
|
}
|
|
}
|
|
|
|
// Use actual font name from metadata for game fonts...
|
|
for (Common::ArchiveMemberList::const_iterator it = fontFiles.begin();
|
|
it != fontFiles.end(); ++it) {
|
|
Common::String name = (*it)->getName();
|
|
Common::String fileBaseName = name.substr(0, name.size() - 4);
|
|
|
|
Common::SeekableReadStream *stream = SearchMan.createReadStreamForMember(Common::Path(name));
|
|
if (stream) {
|
|
Graphics::Font *testFont = Graphics::loadTTFFont(stream,
|
|
DisposeAfterUse::YES,
|
|
12,
|
|
Graphics::kTTFSizeModeCharacter
|
|
);
|
|
|
|
if (testFont) {
|
|
HEFontEntry entry;
|
|
entry.fileName = fileBaseName;
|
|
entry.fontName = testFont->getFontName();
|
|
|
|
// If there's no metadata name, just fallback to the filename...
|
|
if (entry.fontName.empty()) {
|
|
entry.fontName = fileBaseName;
|
|
}
|
|
|
|
_fontEntries.push_back(entry);
|
|
delete testFont;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Sort alphabetically...
|
|
Common::sort(_fontEntries.begin(), _fontEntries.end(),
|
|
[](const HEFontEntry &a, const HEFontEntry &b) {
|
|
return a.fontName.compareToIgnoreCase(b.fontName) < 0;
|
|
}
|
|
);
|
|
|
|
_fontsEnumerated = true;
|
|
}
|
|
|
|
int HEFont::enumInit() {
|
|
enumerateFonts();
|
|
return _fontEntries.size();
|
|
}
|
|
|
|
void HEFont::enumDestroy() {
|
|
_fontEntries.clear();
|
|
_fontsEnumerated = false;
|
|
}
|
|
|
|
const char *HEFont::enumGet(int index) {
|
|
if (!_fontsEnumerated) {
|
|
debug(1, "HEFont::enumGet(): Font enumeration not initialized");
|
|
return nullptr;
|
|
}
|
|
|
|
if (index < 0 || index >= (int)_fontEntries.size()) {
|
|
debug(1, "HEFont::enumGet(): Index %d out of range (0-%d)", index, (int)_fontEntries.size() - 1);
|
|
return nullptr;
|
|
}
|
|
|
|
return _fontEntries[index].fontName.c_str();
|
|
}
|
|
|
|
int HEFont::enumFind(const char *fontName) {
|
|
if (!_fontsEnumerated) {
|
|
debug(1, "HEFont::enumFind(): Font enumeration not initialized");
|
|
return -1;
|
|
}
|
|
|
|
for (uint i = 0; i < _fontEntries.size(); i++) {
|
|
if (_fontEntries[i].fontName.equalsIgnoreCase(fontName)) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
debug(1, "HEFont::enumFind(): Could not find font '%s'", fontName);
|
|
return -1;
|
|
}
|
|
|
|
} // End of namespace Scumm
|