737 lines
26 KiB
C++
737 lines
26 KiB
C++
//
|
|
// SuperTuxKart - a fun racing game with go-kart
|
|
// Copyright (C) 2016 SuperTuxKart-Team
|
|
//
|
|
// 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, write to the Free Software
|
|
// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
|
|
#include "font/font_with_face.hpp"
|
|
|
|
#include "config/user_config.hpp"
|
|
#include "font/face_ttf.hpp"
|
|
#include "font/font_manager.hpp"
|
|
#include "font/font_settings.hpp"
|
|
#include "graphics/2dutils.hpp"
|
|
#include "graphics/central_settings.hpp"
|
|
#include "graphics/irr_driver.hpp"
|
|
#include "graphics/stk_texture.hpp"
|
|
#include "graphics/stk_tex_manager.hpp"
|
|
#include "guiengine/engine.hpp"
|
|
#include "guiengine/skin.hpp"
|
|
#include "modes/profile_world.hpp"
|
|
#include "utils/string_utils.hpp"
|
|
|
|
#include <array>
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Constructor. It will initialize the \ref m_spritebank and TTF files to use.
|
|
* \param name The name of face, used by irrlicht to distinguish spritebank.
|
|
* \param ttf \ref FaceTTF for this face to use.
|
|
*/
|
|
FontWithFace::FontWithFace(const std::string& name, FaceTTF* ttf)
|
|
{
|
|
m_spritebank = irr_driver->getGUI()->addEmptySpriteBank(name.c_str());
|
|
|
|
assert(m_spritebank != NULL);
|
|
m_spritebank->grab();
|
|
|
|
m_fallback_font = NULL;
|
|
m_fallback_font_scale = 1.0f;
|
|
m_glyph_max_height = 0;
|
|
m_face_ttf = ttf;
|
|
|
|
} // FontWithFace
|
|
// ----------------------------------------------------------------------------
|
|
/** Destructor. Clears the glyph page and sprite bank.
|
|
*/
|
|
FontWithFace::~FontWithFace()
|
|
{
|
|
for (unsigned int i = 0; i < m_spritebank->getTextureCount(); i++)
|
|
{
|
|
STKTexManager::getInstance()->removeTexture(
|
|
static_cast<STKTexture*>(m_spritebank->getTexture(i)));
|
|
}
|
|
m_spritebank->drop();
|
|
m_spritebank = NULL;
|
|
|
|
// To be deleted by font_manager
|
|
m_face_ttf = NULL;
|
|
|
|
} // ~FontWithFace
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Initialize the font structure, but don't load glyph here.
|
|
*/
|
|
void FontWithFace::init()
|
|
{
|
|
setDPI();
|
|
#ifndef SERVER_ONLY
|
|
// Get the max height for this face
|
|
assert(m_face_ttf->getTotalFaces() > 0);
|
|
FT_Face cur_face = m_face_ttf->getFace(0);
|
|
font_manager->checkFTError(FT_Set_Pixel_Sizes(cur_face, 0, getDPI()),
|
|
"setting DPI");
|
|
|
|
for (int i = 32; i < 128; i++)
|
|
{
|
|
// Test all basic latin characters
|
|
const int idx = FT_Get_Char_Index(cur_face, (wchar_t)i);
|
|
if (idx == 0) continue;
|
|
font_manager->checkFTError(FT_Load_Glyph(cur_face, idx,
|
|
FT_LOAD_DEFAULT), "setting max height");
|
|
|
|
const int height = cur_face->glyph->metrics.height / BEARING;
|
|
if (height > m_glyph_max_height)
|
|
m_glyph_max_height = height;
|
|
}
|
|
#endif
|
|
reset();
|
|
} // init
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Clear all the loaded characters, sub-class can do pre-loading of characters
|
|
* after this.
|
|
*/
|
|
void FontWithFace::reset()
|
|
{
|
|
m_new_char_holder.clear();
|
|
m_character_area_map.clear();
|
|
m_character_glyph_info_map.clear();
|
|
for (unsigned int i = 0; i < m_spritebank->getTextureCount(); i++)
|
|
{
|
|
STKTexManager::getInstance()->removeTexture(
|
|
static_cast<STKTexture*>(m_spritebank->getTexture(i)));
|
|
}
|
|
m_spritebank->clear();
|
|
createNewGlyphPage();
|
|
} // reset
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Convert a character to a glyph index in one of the font in \ref m_face_ttf,
|
|
* it will find the first TTF that supports this character, if the final
|
|
* glyph_index is 0, this means such character is not supported by all TTFs in
|
|
* \ref m_face_ttf.
|
|
* \param c The character to be loaded.
|
|
*/
|
|
void FontWithFace::loadGlyphInfo(wchar_t c)
|
|
{
|
|
#ifndef SERVER_ONLY
|
|
unsigned int font_number = 0;
|
|
unsigned int glyph_index = 0;
|
|
while (font_number < m_face_ttf->getTotalFaces())
|
|
{
|
|
glyph_index = FT_Get_Char_Index(m_face_ttf->getFace(font_number), c);
|
|
if (glyph_index > 0) break;
|
|
font_number++;
|
|
}
|
|
m_character_glyph_info_map[c] = GlyphInfo(font_number, glyph_index);
|
|
#endif
|
|
} // loadGlyphInfo
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Create a new glyph page by filling it with transparent content.
|
|
*/
|
|
void FontWithFace::createNewGlyphPage()
|
|
{
|
|
#ifndef SERVER_ONLY
|
|
uint8_t* data = new uint8_t[getGlyphPageSize() * getGlyphPageSize() *
|
|
(CVS->isARBTextureSwizzleUsable() ? 1 : 4)]();
|
|
#else
|
|
uint8_t* data = NULL;
|
|
#endif
|
|
m_current_height = 0;
|
|
m_used_width = 0;
|
|
m_used_height = 0;
|
|
STKTexture* stkt = new STKTexture(data, typeid(*this).name() +
|
|
StringUtils::toString(m_spritebank->getTextureCount()),
|
|
getGlyphPageSize(),
|
|
#ifndef SERVER_ONLY
|
|
CVS->isARBTextureSwizzleUsable()
|
|
#else
|
|
false
|
|
#endif
|
|
);
|
|
m_spritebank->addTexture(STKTexManager::getInstance()->addTexture(stkt));
|
|
} // createNewGlyphPage
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Render a glyph for a character into bitmap and save it into the glyph page.
|
|
* \param c The character to be loaded.
|
|
* \param c \ref GlyphInfo for the character.
|
|
*/
|
|
void FontWithFace::insertGlyph(wchar_t c, const GlyphInfo& gi)
|
|
{
|
|
#ifndef SERVER_ONLY
|
|
assert(gi.glyph_index > 0);
|
|
assert(gi.font_number < m_face_ttf->getTotalFaces());
|
|
FT_Face cur_face = m_face_ttf->getFace(gi.font_number);
|
|
FT_GlyphSlot slot = cur_face->glyph;
|
|
|
|
// Same face may be shared across the different FontWithFace,
|
|
// so reset dpi each time
|
|
font_manager->checkFTError(FT_Set_Pixel_Sizes(cur_face, 0, getDPI()),
|
|
"setting DPI");
|
|
|
|
font_manager->checkFTError(FT_Load_Glyph(cur_face, gi.glyph_index,
|
|
FT_LOAD_DEFAULT), "loading a glyph");
|
|
|
|
font_manager->checkFTError(shapeOutline(&(slot->outline)),
|
|
"shaping outline");
|
|
|
|
font_manager->checkFTError(FT_Render_Glyph(slot, FT_RENDER_MODE_NORMAL),
|
|
"rendering a glyph to bitmap");
|
|
|
|
// Convert to an anti-aliased bitmap
|
|
FT_Bitmap* bits = &(slot->bitmap);
|
|
core::dimension2du texture_size(bits->width + 1, bits->rows + 1);
|
|
if ((m_used_width + texture_size.Width > getGlyphPageSize() &&
|
|
m_used_height + m_current_height + texture_size.Height >
|
|
getGlyphPageSize()) ||
|
|
m_used_height + texture_size.Height > getGlyphPageSize())
|
|
{
|
|
// Add a new glyph page if current one is full
|
|
createNewGlyphPage();
|
|
}
|
|
|
|
// Determine the linebreak location
|
|
if (m_used_width + texture_size.Width > getGlyphPageSize())
|
|
{
|
|
m_used_width = 0;
|
|
m_used_height += m_current_height;
|
|
m_current_height = 0;
|
|
}
|
|
|
|
const unsigned int cur_tex = m_spritebank->getTextureCount() -1;
|
|
if (bits->buffer != NULL && !ProfileWorld::isNoGraphics())
|
|
{
|
|
video::ITexture* tex = m_spritebank->getTexture(cur_tex);
|
|
glBindTexture(GL_TEXTURE_2D, tex->getOpenGLTextureName());
|
|
assert(bits->pixel_mode == FT_PIXEL_MODE_GRAY);
|
|
if (CVS->isARBTextureSwizzleUsable())
|
|
{
|
|
glTexSubImage2D(GL_TEXTURE_2D, 0, m_used_width, m_used_height,
|
|
bits->width, bits->rows, GL_RED, GL_UNSIGNED_BYTE,
|
|
bits->buffer);
|
|
}
|
|
else
|
|
{
|
|
const unsigned int size = bits->width * bits->rows;
|
|
uint8_t* image_data = new uint8_t[size * 4];
|
|
memset(image_data, 255, size * 4);
|
|
for (unsigned int i = 0; i < size; i++)
|
|
image_data[4 * i + 3] = bits->buffer[i];
|
|
glTexSubImage2D(GL_TEXTURE_2D, 0, m_used_width, m_used_height,
|
|
bits->width, bits->rows, GL_RGBA, GL_UNSIGNED_BYTE,
|
|
image_data);
|
|
delete[] image_data;
|
|
}
|
|
if (tex->hasMipMaps())
|
|
glGenerateMipmap(GL_TEXTURE_2D);
|
|
glBindTexture(GL_TEXTURE_2D, 0);
|
|
}
|
|
|
|
// Store the rectangle of current glyph
|
|
gui::SGUISpriteFrame f;
|
|
gui::SGUISprite s;
|
|
core::rect<s32> rectangle(m_used_width, m_used_height,
|
|
m_used_width + bits->width, m_used_height + bits->rows);
|
|
f.rectNumber = m_spritebank->getPositions().size();
|
|
f.textureNumber = cur_tex;
|
|
|
|
// Add frame to sprite
|
|
s.Frames.push_back(f);
|
|
s.frameTime = 0;
|
|
m_spritebank->getPositions().push_back(rectangle);
|
|
m_spritebank->getSprites().push_back(s);
|
|
|
|
// Save glyph metrics
|
|
FontArea a;
|
|
a.advance_x = cur_face->glyph->advance.x / BEARING;
|
|
a.bearing_x = cur_face->glyph->metrics.horiBearingX / BEARING;
|
|
const int cur_height = (cur_face->glyph->metrics.height / BEARING);
|
|
const int cur_offset_y = cur_height -
|
|
(cur_face->glyph->metrics.horiBearingY / BEARING);
|
|
a.offset_y = m_glyph_max_height - cur_height + cur_offset_y;
|
|
a.offset_y_bt = -cur_offset_y;
|
|
a.spriteno = f.rectNumber;
|
|
m_character_area_map[c] = a;
|
|
|
|
// Store used area
|
|
m_used_width += texture_size.Width;
|
|
if (m_current_height < texture_size.Height)
|
|
m_current_height = texture_size.Height;
|
|
#endif
|
|
} // insertGlyph
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Update the supported characters for this font if required.
|
|
*/
|
|
void FontWithFace::updateCharactersList()
|
|
{
|
|
if (m_fallback_font != NULL)
|
|
m_fallback_font->updateCharactersList();
|
|
|
|
if (m_new_char_holder.empty()) return;
|
|
for (const wchar_t& c : m_new_char_holder)
|
|
{
|
|
const GlyphInfo& gi = getGlyphInfo(c);
|
|
insertGlyph(c, gi);
|
|
}
|
|
m_new_char_holder.clear();
|
|
|
|
} // updateCharactersList
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Write the current glyph page in png inside current running directory.
|
|
* Mainly for debug use.
|
|
* \param name The file name.
|
|
*/
|
|
void FontWithFace::dumpGlyphPage(const std::string& name)
|
|
{
|
|
#ifndef SERVER_ONLY
|
|
for (unsigned int i = 0; i < m_spritebank->getTextureCount(); i++)
|
|
{
|
|
video::ITexture* tex = m_spritebank->getTexture(i);
|
|
core::dimension2d<u32> size = tex->getSize();
|
|
video::ECOLOR_FORMAT col_format = tex->getColorFormat();
|
|
void* data = tex->lock();
|
|
video::IImage* image = irr_driver->getVideoDriver()
|
|
->createImageFromData(col_format, size, data,
|
|
true/*ownForeignMemory*/);
|
|
tex->unlock();
|
|
irr_driver->getVideoDriver()->writeImageToFile(image, std::string
|
|
(name + "_" + StringUtils::toString(i) + ".png").c_str());
|
|
image->drop();
|
|
}
|
|
#endif
|
|
} // dumpGlyphPage
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Write the current glyph page in png inside current running directory.
|
|
* Useful in gdb without parameter.
|
|
*/
|
|
void FontWithFace::dumpGlyphPage()
|
|
{
|
|
dumpGlyphPage("face");
|
|
} // dumpGlyphPage
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Set the face dpi which is resolution-dependent.
|
|
* Normal text will range from 0.8, in 640x* resolutions (won't scale below
|
|
* that) to 1.0, in 1024x* resolutions, and linearly up.
|
|
* Bold text will range from 0.2, in 640x* resolutions (won't scale below
|
|
* that) to 0.4, in 1024x* resolutions, and linearly up.
|
|
*/
|
|
void FontWithFace::setDPI()
|
|
{
|
|
const int screen_width = irr_driver->getFrameSize().Width;
|
|
const int screen_height = irr_driver->getFrameSize().Height;
|
|
|
|
if (UserConfigParams::m_hidpi_enabled)
|
|
{
|
|
float scale = screen_height / 480.0f;
|
|
m_face_dpi = int(getScalingFactorTwo() * getScalingFactorOne() * scale);
|
|
}
|
|
else
|
|
{
|
|
float scale = std::max(0, screen_width - 640) / 564.0f;
|
|
|
|
// attempt to compensate for small screens
|
|
if (screen_width < 1200)
|
|
scale = std::max(0, screen_width - 640) / 750.0f;
|
|
if (screen_width < 900 || screen_height < 700)
|
|
scale = std::min(scale, 0.05f);
|
|
|
|
m_face_dpi = unsigned((getScalingFactorOne() + 0.2f * scale) *
|
|
getScalingFactorTwo());
|
|
}
|
|
} // setDPI
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Return the \ref FontArea about a character.
|
|
* \param c The character to get.
|
|
* \param[out] fallback_font Whether fallback font is used.
|
|
*/
|
|
const FontWithFace::FontArea&
|
|
FontWithFace::getAreaFromCharacter(const wchar_t c,
|
|
bool* fallback_font) const
|
|
{
|
|
std::map<wchar_t, FontArea>::const_iterator n =
|
|
m_character_area_map.find(c);
|
|
if (n != m_character_area_map.end())
|
|
{
|
|
if (fallback_font != NULL)
|
|
*fallback_font = false;
|
|
return n->second;
|
|
}
|
|
else if (m_fallback_font != NULL && fallback_font != NULL)
|
|
{
|
|
*fallback_font = true;
|
|
return m_fallback_font->getAreaFromCharacter(c, NULL);
|
|
}
|
|
|
|
// Not found, return the first font area, which is a white-space
|
|
if (fallback_font != NULL)
|
|
*fallback_font = false;
|
|
return m_character_area_map.begin()->second;
|
|
|
|
} // getAreaFromCharacter
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Get the dimension of text with support to different \ref FontSettings,
|
|
* it will also do checking for missing characters in font and lazy load them.
|
|
* \param text The text to be calculated.
|
|
* \param font_settings \ref FontSettings to use.
|
|
* \return The dimension of text
|
|
*/
|
|
core::dimension2d<u32> FontWithFace::getDimension(const wchar_t* text,
|
|
FontSettings* font_settings)
|
|
{
|
|
#ifdef SERVER_ONLY
|
|
return core::dimension2d<u32>(1, 1);
|
|
#else
|
|
|
|
const float scale = font_settings ? font_settings->getScale() : 1.0f;
|
|
// Test if lazy load char is needed
|
|
insertCharacters(text);
|
|
updateCharactersList();
|
|
|
|
assert(m_character_area_map.size() > 0);
|
|
core::dimension2d<float> dim(0.0f, 0.0f);
|
|
core::dimension2d<float> this_line(0.0f, m_font_max_height * scale);
|
|
|
|
for (const wchar_t* p = text; *p; ++p)
|
|
{
|
|
if (*p == L'\r' || // Windows breaks
|
|
*p == L'\n' ) // Unix breaks
|
|
{
|
|
if (*p==L'\r' && p[1] == L'\n') // Windows breaks
|
|
++p;
|
|
dim.Height += this_line.Height;
|
|
if (dim.Width < this_line.Width)
|
|
dim.Width = this_line.Width;
|
|
this_line.Width = 0;
|
|
continue;
|
|
}
|
|
|
|
bool fallback = false;
|
|
const FontArea &area = getAreaFromCharacter(*p, &fallback);
|
|
|
|
this_line.Width += getCharWidth(area, fallback, scale);
|
|
}
|
|
|
|
dim.Height += this_line.Height;
|
|
if (dim.Width < this_line.Width)
|
|
dim.Width = this_line.Width;
|
|
|
|
core::dimension2d<u32> ret_dim(0, 0);
|
|
ret_dim.Width = (u32)(dim.Width + 0.9f); // round up
|
|
ret_dim.Height = (u32)(dim.Height + 0.9f);
|
|
|
|
return ret_dim;
|
|
#endif
|
|
} // getDimension
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Calculate the index of the character in the text on a specific position.
|
|
* \param text The text to be calculated.
|
|
* \param pixel_x The specific position.
|
|
* \param font_settings \ref FontSettings to use.
|
|
* \return The index of the character, -1 means no character in such position.
|
|
*/
|
|
int FontWithFace::getCharacterFromPos(const wchar_t* text, int pixel_x,
|
|
FontSettings* font_settings) const
|
|
{
|
|
const float scale = font_settings ? font_settings->getScale() : 1.0f;
|
|
float x = 0;
|
|
int idx = 0;
|
|
|
|
while (text[idx])
|
|
{
|
|
bool use_fallback_font = false;
|
|
const FontArea &a = getAreaFromCharacter(text[idx],
|
|
&use_fallback_font);
|
|
|
|
x += getCharWidth(a, use_fallback_font, scale);
|
|
|
|
if (x >= float(pixel_x))
|
|
return idx;
|
|
|
|
++idx;
|
|
}
|
|
|
|
return -1;
|
|
} // getCharacterFromPos
|
|
|
|
// ----------------------------------------------------------------------------
|
|
/** Render text and clip it to the specified rectangle if wanted, it will also
|
|
* do checking for missing characters in font and lazy load them.
|
|
* \param text The text to be rendering.
|
|
* \param position The position to be rendering.
|
|
* \param color The color used when rendering.
|
|
* \param hcenter If rendered horizontally center.
|
|
* \param vcenter If rendered vertically center.
|
|
* \param clip If clipping is needed.
|
|
* \param font_settings \ref FontSettings to use.
|
|
* \param char_collector \ref FontCharCollector to render billboard text.
|
|
*/
|
|
void FontWithFace::render(const core::stringw& text,
|
|
const core::rect<s32>& position,
|
|
const video::SColor& color, bool hcenter,
|
|
bool vcenter, const core::rect<s32>* clip,
|
|
FontSettings* font_settings,
|
|
FontCharCollector* char_collector)
|
|
{
|
|
#ifndef SERVER_ONLY
|
|
const bool black_border = font_settings ?
|
|
font_settings->useBlackBorder() : false;
|
|
const bool rtl = font_settings ? font_settings->isRTL() : false;
|
|
const float scale = font_settings ? font_settings->getScale() : 1.0f;
|
|
const float shadow = font_settings ? font_settings->useShadow() : false;
|
|
|
|
if (shadow)
|
|
{
|
|
assert(font_settings);
|
|
// Avoid infinite recursion
|
|
font_settings->setShadow(false);
|
|
|
|
core::rect<s32> shadowpos = position;
|
|
shadowpos.LowerRightCorner.X += 2;
|
|
shadowpos.LowerRightCorner.Y += 2;
|
|
render(text, shadowpos, font_settings->getShadowColor(), hcenter,
|
|
vcenter, clip, font_settings);
|
|
|
|
// Set back
|
|
font_settings->setShadow(true);
|
|
}
|
|
|
|
core::position2d<float> offset(float(position.UpperLeftCorner.X),
|
|
float(position.UpperLeftCorner.Y));
|
|
core::dimension2d<s32> text_dimension;
|
|
|
|
if (rtl || hcenter || vcenter || clip)
|
|
{
|
|
text_dimension = getDimension(text.c_str(), font_settings);
|
|
|
|
if (hcenter)
|
|
offset.X += (position.getWidth() - text_dimension.Width) / 2;
|
|
else if (rtl)
|
|
offset.X += (position.getWidth() - text_dimension.Width);
|
|
|
|
if (vcenter)
|
|
offset.Y += (position.getHeight() - text_dimension.Height) / 2;
|
|
if (clip)
|
|
{
|
|
core::rect<s32> clippedRect(core::position2d<s32>
|
|
(s32(offset.X), s32(offset.Y)), text_dimension);
|
|
clippedRect.clipAgainst(*clip);
|
|
if (!clippedRect.isValid()) return;
|
|
}
|
|
}
|
|
|
|
// Collect character locations
|
|
const unsigned int text_size = text.size();
|
|
core::array<s32> indices(text_size);
|
|
core::array<core::position2d<float>> offsets(text_size);
|
|
std::vector<bool> fallback(text_size);
|
|
|
|
// Test again if lazy load char is needed,
|
|
// as some text isn't drawn with getDimension
|
|
insertCharacters(text.c_str());
|
|
updateCharactersList();
|
|
|
|
for (u32 i = 0; i < text_size; i++)
|
|
{
|
|
wchar_t c = text[i];
|
|
|
|
if (c == L'\r' || // Windows breaks
|
|
c == L'\n' ) // Unix breaks
|
|
{
|
|
if (c==L'\r' && text[i+1]==L'\n')
|
|
c = text[++i];
|
|
offset.Y += m_font_max_height * scale;
|
|
offset.X = float(position.UpperLeftCorner.X);
|
|
if (hcenter)
|
|
offset.X += (position.getWidth() - text_dimension.Width) >> 1;
|
|
continue;
|
|
} // if lineBreak
|
|
|
|
bool use_fallback_font = false;
|
|
const FontArea &area = getAreaFromCharacter(c, &use_fallback_font);
|
|
fallback[i] = use_fallback_font;
|
|
if (char_collector == NULL)
|
|
{
|
|
float glyph_offset_x = area.bearing_x *
|
|
(fallback[i] ? m_fallback_font_scale : scale);
|
|
float glyph_offset_y = area.offset_y *
|
|
(fallback[i] ? m_fallback_font_scale : scale);
|
|
offset.X += glyph_offset_x;
|
|
offset.Y += glyph_offset_y;
|
|
offsets.push_back(offset);
|
|
offset.X -= glyph_offset_x;
|
|
offset.Y -= glyph_offset_y;
|
|
}
|
|
else
|
|
{
|
|
// Billboard text specific, use offset_y_bt instead
|
|
float glyph_offset_x = area.bearing_x *
|
|
(fallback[i] ? m_fallback_font_scale : scale);
|
|
float glyph_offset_y = area.offset_y_bt *
|
|
(fallback[i] ? m_fallback_font_scale : scale);
|
|
offset.X += glyph_offset_x;
|
|
offset.Y += glyph_offset_y;
|
|
offsets.push_back(offset);
|
|
offset.X -= glyph_offset_x;
|
|
offset.Y -= glyph_offset_y;
|
|
}
|
|
|
|
indices.push_back(area.spriteno);
|
|
offset.X += getCharWidth(area, fallback[i], scale);
|
|
} // for i < text_size
|
|
|
|
// Do the actual rendering
|
|
const int indice_amount = indices.size();
|
|
core::array<gui::SGUISprite>& sprites = m_spritebank->getSprites();
|
|
core::array<core::rect<s32>>& positions = m_spritebank->getPositions();
|
|
core::array<gui::SGUISprite>* fallback_sprites;
|
|
core::array<core::rect<s32>>* fallback_positions;
|
|
if (m_fallback_font != NULL)
|
|
{
|
|
fallback_sprites = &m_fallback_font->m_spritebank->getSprites();
|
|
fallback_positions = &m_fallback_font->m_spritebank->getPositions();
|
|
}
|
|
else
|
|
{
|
|
fallback_sprites = NULL;
|
|
fallback_positions = NULL;
|
|
}
|
|
|
|
const int sprite_amount = sprites.size();
|
|
|
|
if ((black_border || isBold()) && char_collector == NULL)
|
|
{
|
|
// Draw black border first, to make it behind the real character
|
|
// which make script language display better
|
|
video::SColor black(color.getAlpha(),0,0,0);
|
|
for (int n = 0; n < indice_amount; n++)
|
|
{
|
|
const int sprite_id = indices[n];
|
|
if (!fallback[n] && (sprite_id < 0 || sprite_id >= sprite_amount))
|
|
continue;
|
|
if (indices[n] == -1) continue;
|
|
|
|
const int tex_id = (fallback[n] ?
|
|
(*fallback_sprites)[sprite_id].Frames[0].textureNumber :
|
|
sprites[sprite_id].Frames[0].textureNumber);
|
|
|
|
core::rect<s32> source = (fallback[n] ? (*fallback_positions)
|
|
[(*fallback_sprites)[sprite_id].Frames[0].rectNumber] :
|
|
positions[sprites[sprite_id].Frames[0].rectNumber]);
|
|
|
|
core::dimension2d<float> size(0.0f, 0.0f);
|
|
|
|
float cur_scale = (fallback[n] ? m_fallback_font_scale : scale);
|
|
size.Width = source.getSize().Width * cur_scale;
|
|
size.Height = source.getSize().Height * cur_scale;
|
|
|
|
core::rect<float> dest(offsets[n], size);
|
|
|
|
video::ITexture* texture = (fallback[n] ?
|
|
m_fallback_font->m_spritebank->getTexture(tex_id) :
|
|
m_spritebank->getTexture(tex_id));
|
|
|
|
for (int x_delta = -2; x_delta <= 2; x_delta++)
|
|
{
|
|
for (int y_delta = -2; y_delta <= 2; y_delta++)
|
|
{
|
|
if (x_delta == 0 || y_delta == 0) continue;
|
|
draw2DImage(texture, dest + core::position2d<float>
|
|
(float(x_delta), float(y_delta)), source, clip,
|
|
black, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for (int n = 0; n < indice_amount; n++)
|
|
{
|
|
const int sprite_id = indices[n];
|
|
if (!fallback[n] && (sprite_id < 0 || sprite_id >= sprite_amount))
|
|
continue;
|
|
if (indices[n] == -1) continue;
|
|
|
|
const int tex_id = (fallback[n] ?
|
|
(*fallback_sprites)[sprite_id].Frames[0].textureNumber :
|
|
sprites[sprite_id].Frames[0].textureNumber);
|
|
|
|
core::rect<s32> source = (fallback[n] ?
|
|
(*fallback_positions)[(*fallback_sprites)[sprite_id].Frames[0]
|
|
.rectNumber] : positions[sprites[sprite_id].Frames[0].rectNumber]);
|
|
|
|
core::dimension2d<float> size(0.0f, 0.0f);
|
|
|
|
float cur_scale = (fallback[n] ? m_fallback_font_scale : scale);
|
|
size.Width = source.getSize().Width * cur_scale;
|
|
size.Height = source.getSize().Height * cur_scale;
|
|
|
|
core::rect<float> dest(offsets[n], size);
|
|
|
|
video::ITexture* texture = (fallback[n] ?
|
|
m_fallback_font->m_spritebank->getTexture(tex_id) :
|
|
m_spritebank->getTexture(tex_id));
|
|
|
|
if (fallback[n] || isBold())
|
|
{
|
|
video::SColor top = GUIEngine::getSkin()->getColor("font::top");
|
|
video::SColor bottom = GUIEngine::getSkin()
|
|
->getColor("font::bottom");
|
|
top.setAlpha(color.getAlpha());
|
|
bottom.setAlpha(color.getAlpha());
|
|
|
|
std::array<video::SColor, 4> title_colors;
|
|
if (CVS->isGLSL())
|
|
{
|
|
title_colors = { { top, bottom, top, bottom } };
|
|
}
|
|
else
|
|
{
|
|
title_colors = { { bottom, top, top, bottom } };
|
|
}
|
|
if (char_collector != NULL)
|
|
{
|
|
char_collector->collectChar(texture, dest, source,
|
|
title_colors.data());
|
|
}
|
|
else
|
|
{
|
|
draw2DImage(texture, dest, source, clip, title_colors.data(),
|
|
true);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (char_collector != NULL)
|
|
{
|
|
video::SColor colors[] = {color, color, color, color};
|
|
char_collector->collectChar(texture, dest, source, colors);
|
|
}
|
|
else
|
|
{
|
|
draw2DImage(texture, dest, source, clip, color, true);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
} // render
|