Initial commit
This commit is contained in:
383
engines/twp/font.cpp
Normal file
383
engines/twp/font.cpp
Normal file
@@ -0,0 +1,383 @@
|
||||
/* 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 "twp/twp.h"
|
||||
#include "twp/detection.h"
|
||||
#include "twp/font.h"
|
||||
#include "twp/ggpack.h"
|
||||
#include "twp/resmanager.h"
|
||||
|
||||
namespace Twp {
|
||||
|
||||
enum TokenId {
|
||||
tiWhitespace,
|
||||
tiString,
|
||||
tiColor,
|
||||
tiNewLine,
|
||||
tiEnd
|
||||
};
|
||||
|
||||
typedef struct Token {
|
||||
TokenId id;
|
||||
int startOff, endOff;
|
||||
} Token;
|
||||
|
||||
typedef struct CharInfo {
|
||||
CodePoint chr;
|
||||
Math::Vector2d pos;
|
||||
Color color;
|
||||
Glyph glyph;
|
||||
} CharInfo;
|
||||
|
||||
typedef struct Line {
|
||||
Common::Array<Token> tokens;
|
||||
Common::Array<CharInfo> charInfos;
|
||||
} Line;
|
||||
|
||||
class TokenReader {
|
||||
public:
|
||||
explicit TokenReader(const Common::U32String &text);
|
||||
Common::U32String substr(Token tok);
|
||||
bool readToken(Token &token);
|
||||
|
||||
private:
|
||||
CodePoint readChar();
|
||||
TokenId readTokenId();
|
||||
|
||||
private:
|
||||
Common::U32String _text;
|
||||
size_t _off;
|
||||
};
|
||||
|
||||
static Math::Vector2d normalize(Texture *texture, Math::Vector2d v) {
|
||||
Math::Vector2d textureSize(texture->width, texture->height);
|
||||
return Math::Vector2d(v.getX() / textureSize.getX(), v.getY() / textureSize.getY());
|
||||
}
|
||||
|
||||
static void addGlyphQuad(Texture *texture, Common::Array<Vertex> &vertices, CharInfo info) {
|
||||
// Add a glyph quad to the vertex array
|
||||
|
||||
float left = info.glyph.bounds.left;
|
||||
float top = info.glyph.bounds.bottom;
|
||||
float right = info.glyph.bounds.right;
|
||||
float bottom = info.glyph.bounds.top;
|
||||
|
||||
Math::Vector2d uv1 = normalize(texture, Math::Vector2d(info.glyph.textureRect.left, info.glyph.textureRect.bottom));
|
||||
Math::Vector2d uv2 = normalize(texture, Math::Vector2d(info.glyph.textureRect.right, info.glyph.textureRect.top));
|
||||
|
||||
vertices.push_back(Vertex{Math::Vector2d(info.pos.getX() + left, info.pos.getY() + top), info.color, Math::Vector2d(uv1.getX(), uv2.getY())});
|
||||
vertices.push_back(Vertex{Math::Vector2d(info.pos.getX() + right, info.pos.getY() + top), info.color, Math::Vector2d(uv2.getX(), uv2.getY())});
|
||||
vertices.push_back(Vertex{Math::Vector2d(info.pos.getX() + left, info.pos.getY() + bottom), info.color, Math::Vector2d(uv1.getX(), uv1.getY())});
|
||||
vertices.push_back(Vertex{Math::Vector2d(info.pos.getX() + left, info.pos.getY() + bottom), info.color, Math::Vector2d(uv1.getX(), uv1.getY())});
|
||||
vertices.push_back(Vertex{Math::Vector2d(info.pos.getX() + right, info.pos.getY() + top), info.color, Math::Vector2d(uv2.getX(), uv2.getY())});
|
||||
vertices.push_back(Vertex{Math::Vector2d(info.pos.getX() + right, info.pos.getY() + bottom), info.color, Math::Vector2d(uv2.getX(), uv1.getY())});
|
||||
}
|
||||
|
||||
// Skips all characters while one char from the set `token` is found.
|
||||
// Returns number of characters skipped.
|
||||
static int skipWhile(const Common::U32String &s, const char *toSkip, int start = 0) {
|
||||
int result = 0;
|
||||
int len = s.size();
|
||||
while ((start + result < len) && strchr(toSkip, s[result + start]))
|
||||
result++;
|
||||
return result;
|
||||
}
|
||||
|
||||
static int skipUntil(const Common::U32String &s, const char *until, int start = 0) {
|
||||
int result = 0;
|
||||
int len = s.size();
|
||||
while ((start + result < len) && !strchr(until, s[result + start]))
|
||||
result++;
|
||||
return result;
|
||||
}
|
||||
|
||||
static float width(Text &text, TokenReader &reader, Token tok) {
|
||||
float result = 0;
|
||||
Common::String s = reader.substr(tok);
|
||||
for (size_t i = 0; i < s.size(); i++) {
|
||||
char c = s[i];
|
||||
result += text.getFont()->getGlyph(c).advance;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
TokenReader::TokenReader(const Common::U32String &text) : _text(text), _off(0) {
|
||||
}
|
||||
|
||||
Common::U32String TokenReader::substr(Token tok) {
|
||||
return _text.substr(tok.startOff, tok.endOff - tok.startOff + 1);
|
||||
}
|
||||
|
||||
CodePoint TokenReader::readChar() {
|
||||
char result = _text[_off];
|
||||
_off++;
|
||||
return result;
|
||||
}
|
||||
|
||||
TokenId TokenReader::readTokenId() {
|
||||
const char Whitespace[] = {' ', '\t', '\v', '\r', '\f', '\0'};
|
||||
const char Whitespace2[] = {' ', '\t', '\v', '\r', '\f', '#', '\n', '\0'};
|
||||
if (_off < _text.size()) {
|
||||
char c = readChar();
|
||||
switch (c) {
|
||||
case '\n':
|
||||
return tiNewLine;
|
||||
case '\t':
|
||||
case ' ':
|
||||
_off += skipWhile(_text, Whitespace, _off);
|
||||
return tiWhitespace;
|
||||
case '#':
|
||||
_off += 7;
|
||||
return tiColor;
|
||||
default:
|
||||
_off += skipUntil(_text, Whitespace2, _off);
|
||||
return tiString;
|
||||
}
|
||||
} else {
|
||||
return tiEnd;
|
||||
}
|
||||
}
|
||||
|
||||
bool TokenReader::readToken(Token &token) {
|
||||
int start = _off;
|
||||
TokenId id = readTokenId();
|
||||
if (id != tiEnd) {
|
||||
token.id = id;
|
||||
token.startOff = start;
|
||||
token.endOff = _off - 1;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
GGFont::~GGFont() {}
|
||||
|
||||
void GGFont::load(const Common::String &path) {
|
||||
SpriteSheet *spritesheet = g_twp->_resManager->spriteSheet(path);
|
||||
int lineHeight = 0;
|
||||
for (auto it = spritesheet->_frameTable.begin(); it != spritesheet->_frameTable.end(); it++) {
|
||||
const SpriteSheetFrame &frame = it->_value;
|
||||
Glyph glyph;
|
||||
glyph.advance = MAX(frame.sourceSize.getX() - frame.spriteSourceSize.left - 4, 0.f);
|
||||
glyph.bounds = Common::Rect(Common::Point(frame.spriteSourceSize.left, frame.sourceSize.getY() - frame.spriteSourceSize.height() - frame.spriteSourceSize.top), frame.spriteSourceSize.width(), frame.spriteSourceSize.height());
|
||||
lineHeight = MAX(lineHeight, (int)frame.spriteSourceSize.top);
|
||||
glyph.textureRect = frame.frame;
|
||||
_glyphs[it->_key.asUint64()] = glyph;
|
||||
}
|
||||
_lineHeight = lineHeight;
|
||||
_name = path;
|
||||
}
|
||||
|
||||
Glyph GGFont::getGlyph(CodePoint chr) {
|
||||
int key = (int)chr;
|
||||
if (_glyphs.contains(key)) {
|
||||
return _glyphs[key];
|
||||
}
|
||||
return _glyphs['?'];
|
||||
}
|
||||
|
||||
BmFont::~BmFont() {}
|
||||
|
||||
void BmFont::load(const Common::String &name) {
|
||||
Common::String path = name + ".fnt";
|
||||
if (!g_twp->_pack->assetExists(path.c_str())) {
|
||||
path = name + "Font.fnt";
|
||||
}
|
||||
debugC(kDebugRes, "Load font %s", path.c_str());
|
||||
GGPackEntryReader entry;
|
||||
if (!entry.open(*g_twp->_pack, path)) {
|
||||
error("error loading font %s", path.c_str());
|
||||
}
|
||||
char tmp[80];
|
||||
while (!entry.eos()) {
|
||||
Common::String line = entry.readLine();
|
||||
if (line.hasPrefix("common")) {
|
||||
sscanf(line.c_str(), "common lineHeight=%d base=%d scaleW=%d scaleH=%d pages=%d packed=%d", &_lnHeight, &_base, &_scaleW, &_scaleH, &_pages, &_packed);
|
||||
} else if (line.hasPrefix("chars")) {
|
||||
} else if (line.hasPrefix("char")) {
|
||||
Char c;
|
||||
sscanf(line.c_str(), "char id=%d\tx=%d\ty=%d\twidth=%d\theight=%d\txoffset=%d\tyoffset=%d\txadvance=%d\tpage=%d\tchnl=%d\tletter=\"%79s\"", &c.id, &c.x, &c.y, &c.w, &c.h, &c.xoff, &c.yoff, &c.xadv, &c.page, &c.chnl, tmp);
|
||||
_glyphs[c.id] = Glyph{c.xadv,
|
||||
Common::Rect(c.xoff, _lnHeight - c.yoff - c.h, c.xoff + c.w, _lnHeight - c.yoff),
|
||||
Common::Rect(c.x, c.y, c.x + c.w, c.y + c.h)};
|
||||
} else if (line.hasPrefix("kernings")) {
|
||||
} else if (line.hasPrefix("kerning")) {
|
||||
KerningKey key;
|
||||
int amount = 0;
|
||||
sscanf(line.c_str(), "kerning\tfirst=%d\tsecond=%d\tamount=%d", &key.first, &key.second, &amount);
|
||||
_kernings[key] = amount;
|
||||
}
|
||||
}
|
||||
_name = name;
|
||||
}
|
||||
|
||||
Glyph BmFont::getGlyph(CodePoint chr) {
|
||||
if (_glyphs.contains(chr)) {
|
||||
return _glyphs[chr];
|
||||
}
|
||||
return _glyphs['?'];
|
||||
}
|
||||
|
||||
float BmFont::getKerning(CodePoint prev, CodePoint next) {
|
||||
return 0.f;
|
||||
}
|
||||
|
||||
bool operator==(const KerningKey &l, const KerningKey &r) {
|
||||
return l.first == r.first && l.second == r.second;
|
||||
}
|
||||
|
||||
Text::Text(const Common::String &fontName, const Common::String &text, TextHAlignment hAlign, TextVAlignment vAlign, float maxWidth, const Color &color)
|
||||
: _font(NULL), _fontName(fontName), _texture(NULL), _txt(text), _col(color), _hAlign(hAlign), _vAlign(vAlign), _maxW(maxWidth), _dirty(true) {
|
||||
update();
|
||||
}
|
||||
|
||||
Text::Text() {}
|
||||
|
||||
void Text::setFont(const Common::String &fontName) {
|
||||
_fontName = fontName;
|
||||
_dirty = true;
|
||||
}
|
||||
|
||||
Math::Vector2d Text::getBounds() {
|
||||
update();
|
||||
return _bnds;
|
||||
}
|
||||
|
||||
void Text::update() {
|
||||
if (_dirty) {
|
||||
_dirty = false;
|
||||
_font = g_twp->_resManager->font(_fontName);
|
||||
_texture = g_twp->_resManager->texture(_font->getName() + ".png");
|
||||
|
||||
// Reset
|
||||
_vertices.clear();
|
||||
_quads.clear();
|
||||
_bnds = Math::Vector2d();
|
||||
Color color = _col;
|
||||
|
||||
// split text by tokens and split tokens by lines
|
||||
Common::Array<Line> lines;
|
||||
Line line1;
|
||||
TokenReader reader(_txt);
|
||||
float x = 0;
|
||||
Token tok;
|
||||
while (reader.readToken(tok)) {
|
||||
// ignore color token width
|
||||
float w = tok.id == tiColor || tok.id == tiNewLine ? 0.f : width(*this, reader, tok);
|
||||
// new line if width > maxWidth or newline character
|
||||
if (tok.id == tiNewLine || ((_maxW > 0) && (line1.tokens.size() > 0) && (x + w > _maxW))) {
|
||||
lines.push_back(line1);
|
||||
line1.tokens.clear();
|
||||
x = 0;
|
||||
}
|
||||
if (tok.id != tiNewLine) {
|
||||
if (line1.tokens.size() != 0 || tok.id != tiWhitespace) {
|
||||
line1.tokens.push_back(tok);
|
||||
x += w;
|
||||
}
|
||||
}
|
||||
}
|
||||
lines.push_back(line1);
|
||||
|
||||
// create quads for all characters
|
||||
float maxW = 0.f;
|
||||
float lineHeight = _font->getLineHeight();
|
||||
float y = 0.f;
|
||||
for (size_t i = 0; i < lines.size(); i++) {
|
||||
Line &line = lines[i];
|
||||
CodePoint prevChar = 0;
|
||||
x = 0;
|
||||
for (size_t j = 0; j < line.tokens.size(); j++) {
|
||||
tok = line.tokens[j];
|
||||
if (tok.id == tiColor) {
|
||||
uint iColor;
|
||||
Common::String s = reader.substr(tok);
|
||||
sscanf(s.c_str() + 1, "%x", &iColor);
|
||||
color = Color::withAlpha(Color::rgb((int)(iColor & 0x00FFFFFF)), color.rgba.a);
|
||||
} else {
|
||||
Common::U32String s = reader.substr(tok);
|
||||
for (size_t k = 0; k < s.size(); k++) {
|
||||
CodePoint c = s[k];
|
||||
Glyph glyph = _font->getGlyph(c);
|
||||
float kern = _font->getKerning(prevChar, c);
|
||||
prevChar = c;
|
||||
line.charInfos.push_back(CharInfo{c, Math::Vector2d(x + kern, y), color, glyph});
|
||||
// self.quads.add(rect(x, y, glyph.bounds.x.float32 + glyph.bounds.w.float32, lineHeight));
|
||||
x += (float)glyph.advance;
|
||||
}
|
||||
}
|
||||
}
|
||||
_quads.push_back(Common::Rect(Common::Point(0.0f, y), x, lineHeight));
|
||||
maxW = MAX(maxW, x);
|
||||
y -= lineHeight;
|
||||
}
|
||||
|
||||
// Align text
|
||||
if (_hAlign == thRight) {
|
||||
for (size_t i = 0; i < lines.size(); i++) {
|
||||
float w = maxW - _quads[i].width();
|
||||
for (size_t j = 0; j < lines[i].charInfos.size(); j++) {
|
||||
CharInfo &info = lines[i].charInfos[j];
|
||||
info.pos.setX(info.pos.getX() + w);
|
||||
}
|
||||
}
|
||||
} else if (_hAlign == thCenter) {
|
||||
for (size_t i = 0; i < lines.size(); i++) {
|
||||
float w = maxW - _quads[i].width();
|
||||
for (size_t j = 0; j < lines[i].charInfos.size(); j++) {
|
||||
CharInfo &info = lines[i].charInfos[j];
|
||||
info.pos.setX(info.pos.getX() + w / 2.f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add the glyphs to the vertices
|
||||
for (size_t i = 0; i < lines.size(); i++) {
|
||||
for (size_t j = 0; j < lines[i].charInfos.size(); j++) {
|
||||
const CharInfo &info = lines[i].charInfos[j];
|
||||
addGlyphQuad(_texture, _vertices, info);
|
||||
}
|
||||
}
|
||||
|
||||
_bnds = Math::Vector2d(maxW, lines.size() * _font->getLineHeight());
|
||||
}
|
||||
}
|
||||
|
||||
void Text::draw(Gfx &gfx, const Math::Matrix4 &t) {
|
||||
Math::Matrix4 trsf(t);
|
||||
switch (_vAlign) {
|
||||
case tvTop:
|
||||
trsf.translate(Math::Vector3d(0.f, 0.f, 0.f));
|
||||
break;
|
||||
case tvCenter:
|
||||
trsf.translate(Math::Vector3d(0.f, _bnds.getY() / 2.f, 0.f));
|
||||
break;
|
||||
case tvBottom:
|
||||
trsf.translate(Math::Vector3d(0.f, _bnds.getY(), 0.f));
|
||||
break;
|
||||
}
|
||||
if (_font && !_txt.empty()) {
|
||||
update();
|
||||
gfx.drawPrimitives(GL_TRIANGLES, _vertices.begin(), _vertices.size(), trsf, _texture);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Twp
|
||||
Reference in New Issue
Block a user