/* 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 . * */ #include "ultima/ultima8/misc/common_types.h" #include "ultima/ultima8/gfx/fonts/tt_font.h" #include "ultima/ultima8/gfx/fonts/ttf_rendered_text.h" #include "ultima/ultima8/gfx/texture.h" //include iomanip namespace Ultima { namespace Ultima8 { // various unicode characters which look like small black circles static const uint16 BULLETS[] = { 0x2022, 0x30FB, 0x25CF, 0 }; TTFont::TTFont(Graphics::Font *font, uint32 rgb, int borderSize, bool antiAliased, bool SJIS) : _borderSize(borderSize), _ttfFont(font), _antiAliased(antiAliased), _SJIS(SJIS), _PF_RGBA(Graphics::PixelFormat(4, 8, 8, 8, 8, 24, 16, 8, 0)) { _color = _PF_RGBA.RGBToColor((rgb >> 16) & 0xFF, (rgb >> 8) & 0xff, rgb & 0xff); _bullet = 0; // scan for a character to use as a conversation option _bullet for (int i = 0; BULLETS[i]; ++i) { Common::Rect box = font->getBoundingBox(BULLETS[i]); if (!box.isEmpty()) { _bullet = BULLETS[i]; break; } } if (_bullet == 0) { _bullet = '*'; } } TTFont::~TTFont() { } int TTFont::getHeight() { return _ttfFont->getFontHeight() + 2 * _borderSize; // constant (border) } int TTFont::getBaseline() { Common::Rect box = _ttfFont->getBoundingBox('W'); return box.bottom; } int TTFont::getBaselineSkip() { // TODO: Come up with something more generic than just hardcoding 2 pixel line separation return getHeight() + 2; } template static Common::U32String toUnicode(const Std::string &text, uint16 bullet) { Std::string::size_type len = T::length(text); Common::U32String result = Common::U32String(text.c_str(), len); Std::string::const_iterator iter = text.begin(); for (uint idx = 0; idx < len; ++idx) { uint32 u = T::unicode(iter); if (u == '@') { result.setChar(bullet, idx); } else { result.setChar(u, idx); } } return result; } void TTFont::getStringSize(const Std::string &text, int32 &width, int32 &height) { // convert to unicode Common::U32String unicodeText; if (!_SJIS) unicodeText = toUnicode(text, _bullet); else unicodeText = toUnicode(text, _bullet); width = _ttfFont->getStringWidth(unicodeText); height = _ttfFont->getFontHeight(); width += 2 * _borderSize; height += 2 * _borderSize; } void TTFont::getTextSize(const Std::string &text, int32 &resultWidth, int32 &resultHeight, unsigned int &remaining, int32 width, int32 height, TextAlign align, bool u8specials, bool pagebreaks) { Std::list tmp; if (!_SJIS) tmp = typesetText(this, text, remaining, width, height, align, u8specials, pagebreaks, resultWidth, resultHeight); else tmp = typesetText(this, text, remaining, width, height, align, u8specials, pagebreaks, resultWidth, resultHeight); } void TTFont::addTextBorder(Graphics::ManagedSurface &textSurf, uint32 *texBuf, const Common::Rect32 &dims, int32 resultWidth, int32 resultHeight, uint32 borderColor) { uint8 bA, bR, bG, bB; _PF_RGBA.colorToARGB(borderColor, bA, bR, bG, bB); int sqrSize = _borderSize * _borderSize; int sqrEdge = (_borderSize + 1) * (_borderSize + 1); for (int y = 0; y < textSurf.h; y++) { const byte* surfrow = (const byte*)textSurf.getBasePtr(0, y); for (int x = 0; x < textSurf.w; x++) { if (_antiAliased) { uint32 sColor = *((const uint32 *)(surfrow + x * 4)); uint8 sR, sG, sB, sA; _PF_RGBA.colorToARGB(sColor, sA, sR, sG, sB); if (sA == 0x00) continue; for (int dx = -_borderSize; dx <= _borderSize; dx++) { for (int dy = -_borderSize; dy <= _borderSize; dy++) { int tx = dims.left + x + _borderSize + dx; int ty = dims.top + y + _borderSize + dy; if (tx >= 0 && tx < resultWidth && ty >= 0 && ty < resultHeight) { uint32 dColor = texBuf[ty * resultWidth + tx]; if (borderColor != dColor) { int sqrDist = (dx * dx) + (dy * dy); if (sqrDist < sqrSize) { texBuf[ty * resultWidth + tx] = borderColor; } else if (sqrDist < sqrEdge) { // Blend border color at source intensity with destination uint8 dA, dR, dG, dB; _PF_RGBA.colorToARGB(dColor, dA, dR, dG, dB); double bAlpha = (double)bA / 255.0; double sAlpha = (double)sA / 255.0; double dAlpha = (double)dA / 255.0; dAlpha *= (1.0 - sAlpha); dR = static_cast((bR * sAlpha + dR * dAlpha) / (sAlpha + dAlpha)); dG = static_cast((bG * sAlpha + dG * dAlpha) / (sAlpha + dAlpha)); dB = static_cast((bB * sAlpha + dB * dAlpha) / (sAlpha + dAlpha)); dA = static_cast(255. * bAlpha * (sAlpha + dAlpha)); texBuf[ty * resultWidth + tx] = _PF_RGBA.ARGBToColor(dA, dR, dG, dB); } } } } } } else if (surfrow[x] == 1) { for (int dx = -_borderSize; dx <= _borderSize; dx++) { for (int dy = -_borderSize; dy <= _borderSize; dy++) { int tx = dims.left + x + _borderSize + dx; int ty = dims.top + y + _borderSize + dy; if (tx >= 0 && tx < resultWidth && ty >= 0 && ty < resultHeight) { int sqrDist = (dx * dx) + (dy * dy); if (sqrDist < sqrEdge) { texBuf[ty * resultWidth + tx] = borderColor; } } } } } } } } RenderedText *TTFont::renderText(const Std::string &text, unsigned int &remaining, int32 width, int32 height, TextAlign align, bool u8specials, bool pagebreaks, Std::string::size_type cursor) { int32 resultWidth, resultHeight, lineHeight; Std::list lines; if (!_SJIS) lines = typesetText(this, text, remaining, width, height, align, u8specials, pagebreaks, resultWidth, resultHeight, cursor); else lines = typesetText(this, text, remaining, width, height, align, u8specials, pagebreaks, resultWidth, resultHeight, cursor); lineHeight = _ttfFont->getFontHeight(); uint32 borderColor = _PF_RGBA.ARGBToColor(0xFF, 0x00, 0x00, 0x00); Graphics::ManagedSurface *texture = new Graphics::ManagedSurface(resultWidth, resultHeight, _PF_RGBA); uint32 *texBuf = (uint32 *)texture->getPixels(); for (const auto &line : lines) { // convert to unicode Common::U32String unicodeText; if (!_SJIS) unicodeText = toUnicode(line._text, _bullet); else unicodeText = toUnicode(line._text, _bullet); // Create a surface and render the text Graphics::ManagedSurface textSurf; if (!_antiAliased) { // When not in antialiased mode, use a paletted surface where '1' is // used for pixels of the text textSurf.create(resultWidth, lineHeight, Graphics::PixelFormat::createFormatCLUT8()); _ttfFont->drawString(&textSurf, unicodeText, 0, 0, resultWidth, 1); } else { // Use a high color surface with the specified _color color for text textSurf.create(resultWidth, lineHeight, _PF_RGBA); _ttfFont->drawString(&textSurf, unicodeText, 0, 0, resultWidth, _color); }; // Add border within radius. Pixels on the edge are alpha blended if antialiased if (_borderSize > 0) { addTextBorder(textSurf, texBuf, line._dims, resultWidth, resultHeight, borderColor); } // render the text surface into our texture buffer for (int y = 0; y < textSurf.h; y++) { const byte *surfrow = (const byte *)textSurf.getBasePtr(0, y); int ty = line._dims.top + y + _borderSize; for (int x = 0; x < textSurf.w; x++) { int tx = line._dims.left + x + _borderSize; if (_antiAliased) { uint32 sColor = *((const uint32 *)(surfrow + x * 4)); uint8 sR, sG, sB, sA; _PF_RGBA.colorToARGB(sColor, sA, sR, sG, sB); if (sA == 0xFF) { texBuf[ty * resultWidth + tx] = sColor; } else if (sA != 0x00) { // Blend color with destination int32 dColor = texBuf[ty * resultWidth + tx]; uint8 dA, dR, dG, dB; _PF_RGBA.colorToARGB(dColor, dA, dR, dG, dB); double sAlpha = (double)sA / 255.0; double dAlpha = (double)dA / 255.0; dAlpha *= (1.0 - sAlpha); dR = static_cast((sR * sAlpha + dR * dAlpha) / (sAlpha + dAlpha)); dG = static_cast((sG * sAlpha + dG * dAlpha) / (sAlpha + dAlpha)); dB = static_cast((sB * sAlpha + dB * dAlpha) / (sAlpha + dAlpha)); dA = static_cast(255. * (sAlpha + dAlpha)); texBuf[ty * resultWidth + tx] = _PF_RGBA.ARGBToColor(dA, dR, dG, dB); } } else if (surfrow[x] == 1) { texBuf[ty * resultWidth + tx] = _color; } } } if (line._cursor != Std::string::npos) { assert(line._cursor <= line._text.size()); unicodeText = unicodeText.substr(0, line._cursor); int w = _ttfFont->getStringWidth(unicodeText); for (int y = 0; y < line._dims.height(); y++) { int tx = line._dims.left + w + _borderSize; int ty = line._dims.top + y; texBuf[ty * resultWidth + tx] = borderColor; } } } return new TTFRenderedText(texture, resultWidth, resultHeight, getBaselineSkip() - getHeight(), getBaseline(), isAntialiased()); } } // End of namespace Ultima8 } // End of namespace Ultima