2010-04-23 01:13:44 -07:00
|
|
|
// Kudus to Nalin for this wonderful piece of code!
|
|
|
|
// See: http://irrlicht.sourceforge.net/phpBB2/viewtopic.php?t=3995
|
2019-03-07 19:18:07 -08:00
|
|
|
#include <assert.h>
|
|
|
|
#include <iostream>
|
|
|
|
|
|
|
|
|
2019-03-07 08:12:09 -08:00
|
|
|
#include <irrlicht/irrlicht.h>
|
2010-04-23 01:13:44 -07:00
|
|
|
using namespace irr;
|
|
|
|
#include "CGUITTFont.h"
|
2019-03-07 19:18:07 -08:00
|
|
|
|
|
|
|
using namespace std;
|
2010-04-23 01:13:44 -07:00
|
|
|
|
|
|
|
namespace irr
|
|
|
|
{
|
|
|
|
namespace gui
|
|
|
|
{
|
|
|
|
|
|
|
|
FT_Library library;
|
|
|
|
bool CGUITTFace::libraryLoaded = false;
|
|
|
|
s32 facesCount = 0;
|
|
|
|
|
2019-03-07 19:18:07 -08:00
|
|
|
|
2010-04-23 01:13:44 -07:00
|
|
|
//////////////////////
|
|
|
|
|
|
|
|
CGUITTFace::CGUITTFace()
|
2019-03-07 10:23:54 -08:00
|
|
|
: faceLoaded(false), font_buffer(nullptr), font_size(0)
|
2010-04-23 01:13:44 -07:00
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
CGUITTFace::~CGUITTFace()
|
|
|
|
{
|
2019-03-07 10:23:54 -08:00
|
|
|
if (faceLoaded)
|
|
|
|
{
|
|
|
|
FT_Done_Face(face);
|
|
|
|
if (font_buffer)
|
|
|
|
{
|
|
|
|
delete[] font_buffer;
|
|
|
|
font_buffer = nullptr;
|
|
|
|
}
|
|
|
|
if (--facesCount == 0)
|
|
|
|
{
|
|
|
|
FT_Done_FreeType(library);
|
|
|
|
CGUITTFace::libraryLoaded = false;
|
|
|
|
}
|
|
|
|
}
|
2010-04-23 01:13:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
bool CGUITTFace::load(const io::path& filename, io::IFileSystem* filesystem)
|
|
|
|
{
|
2019-03-07 10:23:54 -08:00
|
|
|
if (!libraryLoaded)
|
|
|
|
{
|
|
|
|
if (FT_Init_FreeType(&library))
|
|
|
|
return false;
|
|
|
|
CGUITTFace::libraryLoaded = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (filesystem)
|
|
|
|
{
|
|
|
|
// Read in the file data.
|
|
|
|
io::IReadFile* file = filesystem->createAndOpenFile(filename);
|
|
|
|
if (file != nullptr) {
|
|
|
|
font_buffer = new FT_Byte[file->getSize()];
|
|
|
|
file->read(font_buffer, file->getSize());
|
|
|
|
font_size = file->getSize();
|
|
|
|
file->drop();
|
|
|
|
|
|
|
|
// Create the face.
|
|
|
|
if (FT_New_Memory_Face(library, font_buffer, font_size, 0, &face))
|
|
|
|
{
|
|
|
|
delete[] font_buffer;
|
|
|
|
font_buffer = nullptr;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (FT_New_Face(library, core::stringc(filename).c_str(), 0, &face))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (FT_New_Face(library, core::stringc(filename).c_str(), 0, &face))
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
++facesCount;
|
|
|
|
faceLoaded = true;
|
|
|
|
return true;
|
2010-04-23 01:13:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////
|
|
|
|
|
|
|
|
CGUITTGlyph::CGUITTGlyph()
|
2019-03-07 10:23:54 -08:00
|
|
|
: cached(false), face(nullptr), image(nullptr), texture(nullptr), texture_mono(nullptr),
|
2010-04-23 01:13:44 -07:00
|
|
|
size(0), size_is_pixels(false), hasDefault(false), hasMonochrome(false)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
CGUITTGlyph::~CGUITTGlyph()
|
|
|
|
{
|
2019-03-07 10:23:54 -08:00
|
|
|
if (image) delete[] image;
|
|
|
|
if (texture) Driver->removeTexture(texture);
|
|
|
|
if (texture_mono) Driver->removeTexture(texture_mono);
|
2010-04-23 01:13:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void CGUITTGlyph::cache(u32 idx, bool fontHinting, bool autoHinting)
|
|
|
|
{
|
2019-03-07 10:23:54 -08:00
|
|
|
// Set the size of the glyph.
|
|
|
|
if (size_is_pixels)
|
|
|
|
FT_Set_Pixel_Sizes(*face, 0, size);
|
|
|
|
else
|
|
|
|
FT_Set_Char_Size(*face, size * 64, size * 64, 0, 0);
|
|
|
|
|
|
|
|
FT_Int32 loadFlags = FT_LOAD_DEFAULT;
|
|
|
|
if (!fontHinting) loadFlags |= FT_LOAD_NO_HINTING;
|
|
|
|
if (!autoHinting) loadFlags |= FT_LOAD_NO_AUTOHINT;
|
|
|
|
if (!FT_Load_Glyph(*face, idx, loadFlags))
|
|
|
|
{
|
|
|
|
hasDefault = true;
|
|
|
|
FT_GlyphSlot glyph = (*face)->glyph;
|
|
|
|
FT_Bitmap bits;
|
|
|
|
FT_Error err = 0;
|
|
|
|
if (glyph->format != FT_GLYPH_FORMAT_BITMAP)
|
|
|
|
err = FT_Render_Glyph(glyph, FT_RENDER_MODE_NORMAL);
|
|
|
|
|
|
|
|
if (!err)
|
|
|
|
{
|
|
|
|
// Generate the image.
|
|
|
|
bits = glyph->bitmap;
|
|
|
|
u8 *pt = bits.buffer;
|
|
|
|
image = new u8[bits.width * bits.rows];
|
|
|
|
memcpy(image, pt, bits.width * bits.rows);
|
|
|
|
|
|
|
|
// Bitmap information.
|
|
|
|
bitmap.top = glyph->bitmap_top;
|
|
|
|
bitmap.left = glyph->bitmap_left;
|
|
|
|
bitmap.width = bits.width;
|
|
|
|
bitmap.height = bits.rows;
|
|
|
|
|
|
|
|
// Initialize the texture information.
|
|
|
|
texture_size.set(1, 1);
|
|
|
|
|
|
|
|
// Determine what our texture size should be.
|
|
|
|
// Keep multiplying by 2 until we get it.
|
|
|
|
while (texture_size.Width <= bitmap.width)
|
|
|
|
texture_size.Width <<= 1;
|
|
|
|
while (texture_size.Height <= bitmap.height)
|
|
|
|
texture_size.Height <<= 1;
|
|
|
|
|
|
|
|
// Square our texture.
|
|
|
|
if (texture_size.Height < texture_size.Width)
|
|
|
|
texture_size.Height = texture_size.Width;
|
|
|
|
else texture_size.Width = texture_size.Height;
|
|
|
|
|
|
|
|
// Create our texture data.
|
|
|
|
u32 *texture_data = new u32[texture_size.Width * texture_size.Height];
|
|
|
|
memset(texture_data, 0, texture_size.Width * texture_size.Height * sizeof(u32));
|
|
|
|
u32 *texp = texture_data;
|
|
|
|
bool cflag = (Driver->getDriverType() == video::EDT_DIRECT3D8);
|
2021-03-22 07:37:17 -07:00
|
|
|
for (unsigned int i = 0; i < bits.rows; i++)
|
2019-03-07 10:23:54 -08:00
|
|
|
{
|
|
|
|
u32 *rowp = texp;
|
2021-03-22 07:37:17 -07:00
|
|
|
for (unsigned int j = 0; j < bits.width; j++)
|
2019-03-07 10:23:54 -08:00
|
|
|
{
|
|
|
|
if (*pt)
|
|
|
|
{
|
|
|
|
if (cflag)
|
|
|
|
{
|
|
|
|
*rowp = *pt;
|
|
|
|
*rowp *= 0x01010101;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
*rowp = *pt << 24;
|
|
|
|
*rowp |= 0x00ffffff;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else *rowp = 0;
|
|
|
|
pt++;
|
|
|
|
rowp++;
|
|
|
|
}
|
|
|
|
texp += texture_size.Width;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Name our texture.
|
|
|
|
c8 name[128];
|
|
|
|
sprintf(name, "TTFontGlyph%d", idx);
|
|
|
|
|
|
|
|
// Create our texture.
|
|
|
|
video::IImage *img = Driver->createImageFromData(video::ECF_A8R8G8B8, core::dimension2d<u32>(texture_size.Width, texture_size.Height), texture_data);
|
|
|
|
bool flg16 = Driver->getTextureCreationFlag(video::ETCF_ALWAYS_16_BIT);
|
|
|
|
bool flg32 = Driver->getTextureCreationFlag(video::ETCF_ALWAYS_32_BIT);
|
|
|
|
bool flgmip = Driver->getTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS);
|
|
|
|
Driver->setTextureCreationFlag(video::ETCF_ALWAYS_16_BIT, false);
|
|
|
|
Driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, true);
|
|
|
|
Driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, false);
|
|
|
|
if (texture) Driver->removeTexture(texture);
|
|
|
|
texture = Driver->addTexture(name, img);
|
|
|
|
img->drop();
|
|
|
|
Driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS, flgmip);
|
|
|
|
Driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT, flg32);
|
|
|
|
Driver->setTextureCreationFlag(video::ETCF_ALWAYS_16_BIT, flg16);
|
|
|
|
delete[] texture_data;
|
|
|
|
|
|
|
|
// Set our glyph as "cached".
|
|
|
|
cached = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
loadFlags = FT_LOAD_RENDER | FT_LOAD_MONOCHROME;
|
|
|
|
if (!fontHinting) loadFlags |= FT_LOAD_NO_HINTING;
|
|
|
|
if (!autoHinting) loadFlags |= FT_LOAD_NO_AUTOHINT;
|
|
|
|
if (fontHinting || autoHinting) loadFlags |= FT_LOAD_TARGET_MONO;
|
|
|
|
if (!FT_Load_Glyph(*face, idx, loadFlags))
|
|
|
|
{
|
|
|
|
hasMonochrome = true;
|
|
|
|
FT_GlyphSlot glyph = (*face)->glyph;
|
|
|
|
FT_Bitmap bits = glyph->bitmap;
|
|
|
|
u8 *pt = bits.buffer;
|
|
|
|
|
|
|
|
// Bitmap information.
|
|
|
|
bitmap_mono.top = glyph->bitmap_top;
|
|
|
|
bitmap_mono.left = glyph->bitmap_left;
|
|
|
|
bitmap_mono.width = bits.width;
|
|
|
|
bitmap_mono.height = bits.rows;
|
|
|
|
|
|
|
|
// Initialize the texture information.
|
|
|
|
texture_mono_size.set(1, 1);
|
|
|
|
|
|
|
|
// Determine what our texture size should be.
|
|
|
|
// Keep multiplying by 2 until we get it.
|
|
|
|
while (texture_mono_size.Width <= bitmap_mono.width)
|
|
|
|
texture_mono_size.Width <<= 1;
|
|
|
|
while (texture_mono_size.Height <= bitmap_mono.height)
|
|
|
|
texture_mono_size.Height <<= 1;
|
|
|
|
|
|
|
|
// Square our texture.
|
|
|
|
if (texture_mono_size.Height < texture_mono_size.Width)
|
|
|
|
texture_mono_size.Height = texture_mono_size.Width;
|
|
|
|
else texture_mono_size.Width = texture_mono_size.Height;
|
|
|
|
|
|
|
|
// Create our texture data.
|
|
|
|
u16 *texture_mono_data = new u16[texture_mono_size.Width * texture_mono_size.Height];
|
|
|
|
memset(texture_mono_data, 0, texture_mono_size.Width * texture_mono_size.Height * sizeof(u16));
|
|
|
|
u16 *texpm = texture_mono_data;
|
2021-03-22 07:37:17 -07:00
|
|
|
for (unsigned int y = 0; y < bits.rows; y++)
|
2019-03-07 10:23:54 -08:00
|
|
|
{
|
|
|
|
u16 *rowp = texpm;
|
2021-03-22 07:37:17 -07:00
|
|
|
for (unsigned int x = 0; x < bits.width; x++)
|
2019-03-07 10:23:54 -08:00
|
|
|
{
|
|
|
|
if (pt[y * bits.pitch + (x / 8)] & (0x80 >> (x % 8)))
|
|
|
|
*rowp = 0xffff;
|
|
|
|
rowp++;
|
|
|
|
}
|
|
|
|
texpm += texture_mono_size.Width;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Name our texture.
|
|
|
|
c8 name[128];
|
|
|
|
sprintf(name, "TTFontGlyph%d_mono", idx);
|
|
|
|
|
|
|
|
// Create our texture.
|
|
|
|
video::IImage *img = Driver->createImageFromData(video::ECF_A1R5G5B5, core::dimension2d<u32>(texture_mono_size.Width, texture_mono_size.Height), texture_mono_data);
|
|
|
|
bool flg16 = Driver->getTextureCreationFlag(video::ETCF_ALWAYS_16_BIT);
|
|
|
|
bool flg32 = Driver->getTextureCreationFlag(video::ETCF_ALWAYS_32_BIT);
|
|
|
|
bool flgmip = Driver->getTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS);
|
|
|
|
Driver->setTextureCreationFlag(video::ETCF_ALWAYS_16_BIT,true);
|
|
|
|
Driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT,false);
|
|
|
|
Driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS,false);
|
|
|
|
if (texture_mono) Driver->removeTexture(texture_mono);
|
|
|
|
texture_mono = Driver->addTexture(name, img);
|
|
|
|
img->drop();
|
|
|
|
Driver->makeColorKeyTexture(texture_mono, video::SColor(0,0,0,0));
|
|
|
|
Driver->setTextureCreationFlag(video::ETCF_CREATE_MIP_MAPS,flgmip);
|
|
|
|
Driver->setTextureCreationFlag(video::ETCF_ALWAYS_32_BIT,flg32);
|
|
|
|
Driver->setTextureCreationFlag(video::ETCF_ALWAYS_16_BIT,flg16);
|
|
|
|
delete[] texture_mono_data;
|
|
|
|
}
|
2010-04-23 01:13:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
//////////////////////
|
|
|
|
|
|
|
|
//! constructor
|
|
|
|
CGUITTFont::CGUITTFont(IGUIEnvironment *env)
|
|
|
|
: AntiAlias(true), Transparency(true), FontHinting(true), AutoHinting(true),
|
2019-03-07 10:23:54 -08:00
|
|
|
Environment(env), Driver(nullptr), tt_face(nullptr), GlobalKerningWidth(0), GlobalKerningHeight(0)
|
2010-04-23 01:13:44 -07:00
|
|
|
{
|
2019-03-07 10:23:54 -08:00
|
|
|
#ifdef _DEBUG
|
|
|
|
setDebugName("CGUITTFont");
|
|
|
|
#endif
|
2010-04-23 01:13:44 -07:00
|
|
|
|
2019-03-07 10:23:54 -08:00
|
|
|
if (Environment)
|
|
|
|
{
|
|
|
|
// don't grab environment, to avoid circular references
|
|
|
|
Driver = Environment->getVideoDriver();
|
|
|
|
}
|
2010-04-23 01:13:44 -07:00
|
|
|
|
2019-03-07 10:23:54 -08:00
|
|
|
if (Driver)
|
|
|
|
Driver->grab();
|
2010-04-23 01:13:44 -07:00
|
|
|
|
2019-04-11 01:31:04 -07:00
|
|
|
setInvisibleCharacters (L" ");
|
2010-04-23 01:13:44 -07:00
|
|
|
|
2019-03-07 10:23:54 -08:00
|
|
|
// Glyphs isn't reference counted, so don't try to delete when we free the array.
|
|
|
|
Glyphs.set_free_when_destroyed(false);
|
2010-04-23 01:13:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
//! destructor
|
|
|
|
CGUITTFont::~CGUITTFont()
|
|
|
|
{
|
2019-03-07 10:23:54 -08:00
|
|
|
Glyphs.clear();
|
|
|
|
if (Driver)
|
|
|
|
Driver->drop();
|
2010-04-23 01:13:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
bool CGUITTFont::attach(CGUITTFace *face, u32 size, bool size_is_pixels)
|
|
|
|
{
|
2019-03-07 19:18:07 -08:00
|
|
|
if (!Driver) {
|
|
|
|
cerr << "ERROR: tried to attach font Face but there is no Driver" << endl;
|
2019-03-07 10:23:54 -08:00
|
|
|
return false;
|
|
|
|
}
|
2019-03-07 19:18:07 -08:00
|
|
|
if (face == nullptr) {
|
|
|
|
cerr << "ERROR: tried to attach null tt_face" << endl;
|
2019-03-07 10:23:54 -08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (face->face == nullptr) {
|
2019-03-07 19:18:07 -08:00
|
|
|
cerr << "ERROR: tried to attach null tt_face->face" << endl;
|
2019-03-07 10:23:54 -08:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
tt_face = face;
|
|
|
|
|
|
|
|
if (tt_face->face->num_glyphs > 0) {
|
|
|
|
Glyphs.reallocate(tt_face->face->num_glyphs);
|
|
|
|
Glyphs.set_used(tt_face->face->num_glyphs);
|
|
|
|
}
|
|
|
|
for (int i = 0; i < tt_face->face->num_glyphs; i++)
|
|
|
|
{
|
|
|
|
Glyphs[i].Driver = Driver;
|
|
|
|
Glyphs[i].size = size;
|
|
|
|
Glyphs[i].size_is_pixels = size_is_pixels;
|
|
|
|
if (tt_face->face != nullptr)
|
|
|
|
Glyphs[i].face = &(tt_face->face);
|
|
|
|
else
|
|
|
|
Glyphs[i].face = nullptr;
|
|
|
|
Glyphs[i].cached = false;
|
|
|
|
}
|
|
|
|
return true;
|
2010-04-23 01:13:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void CGUITTFont::draw(const core::stringw& text, const core::rect<s32>& position, video::SColor color, bool hcenter, bool vcenter, const core::rect<s32>* clip)
|
|
|
|
{
|
2019-03-07 10:23:54 -08:00
|
|
|
if (!Driver)
|
|
|
|
return;
|
|
|
|
if (tt_face == nullptr || tt_face->face == nullptr)
|
|
|
|
return;
|
2010-04-23 01:13:44 -07:00
|
|
|
|
2019-03-07 10:23:54 -08:00
|
|
|
core::dimension2d<s32> textDimension;
|
|
|
|
core::position2d<s32> offset = position.UpperLeftCorner;
|
|
|
|
video::SColor colors[4];
|
|
|
|
for (int i = 0; i < 4; i++)
|
|
|
|
colors[i] = color;
|
2010-04-23 01:13:44 -07:00
|
|
|
|
|
|
|
if (hcenter || vcenter)
|
2019-03-07 10:23:54 -08:00
|
|
|
{
|
|
|
|
textDimension = getDimension(text.c_str());
|
|
|
|
|
|
|
|
if (hcenter)
|
|
|
|
offset.X = ((position.getWidth() - textDimension.Width) >> 1) + offset.X;
|
|
|
|
|
|
|
|
if (vcenter)
|
|
|
|
offset.Y = ((position.getHeight() - textDimension.Height) >> 1) + offset.Y;
|
|
|
|
}
|
|
|
|
|
|
|
|
u32 n;
|
|
|
|
|
|
|
|
wchar_t previousChar = 0;
|
|
|
|
const wchar_t* ptext = text.c_str();
|
|
|
|
while (*ptext)
|
|
|
|
{
|
|
|
|
wchar_t currentChar = *ptext;
|
|
|
|
n = getGlyphByChar(currentChar);
|
|
|
|
bool visible = (Invisible.findFirst(currentChar) == -1);
|
|
|
|
if (n > 0 && visible)
|
|
|
|
{
|
|
|
|
bool lineBreak=false;
|
|
|
|
if (currentChar == L'\r') // Mac or Windows breaks
|
|
|
|
{
|
|
|
|
lineBreak = true;
|
|
|
|
if (*(ptext + 1) == L'\n') // Windows breaks
|
|
|
|
currentChar = *(++ptext);
|
|
|
|
}
|
|
|
|
else if (currentChar == L'\n') // Unix breaks
|
|
|
|
{
|
|
|
|
lineBreak = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lineBreak)
|
|
|
|
{
|
|
|
|
offset.Y += Glyphs[0].size;
|
|
|
|
offset.X = position.UpperLeftCorner.X;
|
|
|
|
|
|
|
|
if (hcenter)
|
|
|
|
{
|
|
|
|
core::dimension2d<u32> lineDim = getDimension(text.c_str());
|
|
|
|
offset.X += (position.getWidth() - lineDim.Width) >> 1;
|
|
|
|
}
|
|
|
|
++ptext;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only use the 256-color font if we are anti-aliasing, or if there was no monochrome font.
|
|
|
|
if (AntiAlias || !Glyphs[n-1].hasMonochrome)
|
|
|
|
{
|
|
|
|
u32 texw = Glyphs[n-1].texture_size.Width;
|
|
|
|
u32 texh = Glyphs[n-1].texture_size.Height;
|
|
|
|
u32 bmpw = Glyphs[n-1].bitmap.width;
|
|
|
|
u32 bmph = Glyphs[n-1].bitmap.height;
|
|
|
|
s32 offx = Glyphs[n-1].bitmap.left;
|
|
|
|
s32 offy = Glyphs[n-1].size - Glyphs[n-1].bitmap.top;
|
|
|
|
|
|
|
|
// Apply kerning.
|
|
|
|
core::vector2di k = getKerning(currentChar, previousChar);
|
|
|
|
offset.X += k.X;
|
|
|
|
offset.Y += k.Y;
|
|
|
|
|
|
|
|
if (Driver->getDriverType() != video::EDT_SOFTWARE)
|
|
|
|
{
|
|
|
|
if (!Transparency) color.color |= 0xff000000;
|
|
|
|
Driver->draw2DImage(Glyphs[n-1].texture, core::position2d<s32>(offset.X + offx, offset.Y + offy), core::rect<s32>(0, 0, texw-1, texh-1), clip, color, true);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Software driver doesn't do transparency correctly, so if we are using the driver,
|
|
|
|
// we must manually apply the transparency.
|
|
|
|
u32 a = color.getAlpha();
|
|
|
|
u32 r = color.getRed();
|
|
|
|
u32 g = color.getGreen();
|
|
|
|
u32 b = color.getBlue();
|
|
|
|
u8 *pt = Glyphs[n-1].image;
|
|
|
|
if (!Transparency) a = 255;
|
|
|
|
for (u32 y = 0; y < bmph; y++)
|
|
|
|
{
|
|
|
|
for (u32 x = 0; x < bmpw; x++)
|
|
|
|
{
|
|
|
|
if (!clip || clip->isPointInside(core::position2d<s32>(offset.X + x + offx, offset.Y + y + offy)))
|
|
|
|
{
|
|
|
|
if (*pt)
|
|
|
|
{
|
|
|
|
Driver->draw2DRectangle(video::SColor((a * (*pt)) / 255, r, g, b), core::rect<s32>(offset.X + x + offx, offset.Y + y + offy, offset.X + x + offx + 1, offset.Y + y + offy + 1));
|
|
|
|
}
|
|
|
|
pt++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if (Glyphs[n-1].hasMonochrome)
|
|
|
|
{
|
|
|
|
u32 texw = Glyphs[n-1].texture_mono_size.Width;
|
|
|
|
u32 texh = Glyphs[n-1].texture_mono_size.Height;
|
|
|
|
s32 offx = Glyphs[n-1].bitmap_mono.left;
|
|
|
|
s32 offy = Glyphs[n-1].size - Glyphs[n-1].bitmap_mono.top;
|
|
|
|
|
|
|
|
// Apply kerning.
|
|
|
|
core::vector2di k = getKerning(currentChar, previousChar);
|
|
|
|
offset.X += k.X;
|
|
|
|
offset.Y += k.Y;
|
|
|
|
|
|
|
|
if (!Transparency) color.color |= 0xff000000;
|
|
|
|
Driver->draw2DImage(Glyphs[n-1].texture_mono, core::position2d<s32>(offset.X + offx, offset.Y + offy), core::rect<s32>(0, 0, texw - 1, texh - 1), clip, color, true);
|
|
|
|
}
|
|
|
|
offset.X += getWidthFromCharacter(currentChar);
|
|
|
|
}
|
|
|
|
else offset.X += getWidthFromCharacter(currentChar);
|
|
|
|
|
|
|
|
previousChar = currentChar;
|
|
|
|
++ptext;
|
|
|
|
}
|
2010-04-23 01:13:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
core::dimension2d<u32> CGUITTFont::getDimension(const wchar_t* text) const
|
|
|
|
{
|
2019-03-07 10:23:54 -08:00
|
|
|
// Get the maximum font height. Unfortunately, we have to do this hack as
|
|
|
|
// Irrlicht will draw things wrong. In FreeType, the font size is the
|
|
|
|
// maximum size for a single glyph, but that glyph may hang "under" the
|
|
|
|
// draw line, increasing the total font height to beyond the set size.
|
|
|
|
// Irrlicht does not understand this concept when drawing fonts. Also, I
|
|
|
|
// add +1 to give it a 1 pixel blank border. This makes things like
|
|
|
|
// tooltips look nicer.
|
|
|
|
s32 test1 = getHeightFromCharacter(L'g') + 1;
|
|
|
|
s32 test2 = getHeightFromCharacter(L'j') + 1;
|
|
|
|
s32 test3 = getHeightFromCharacter(L'_') + 1;
|
|
|
|
s32 max_font_height = core::max_(test1, core::max_(test2, test3));
|
|
|
|
|
|
|
|
core::dimension2d<u32> dim(0, max_font_height);
|
|
|
|
core::dimension2d<u32> thisLine(0, max_font_height);
|
|
|
|
|
|
|
|
wchar_t previousChar = 0;
|
|
|
|
for (const wchar_t* p = text; *p; ++p)
|
|
|
|
{
|
|
|
|
bool lineBreak=false;
|
|
|
|
if (*p == L'\r') // Mac or Windows breaks
|
|
|
|
{
|
|
|
|
lineBreak = true;
|
|
|
|
if (p[1] == L'\n') // Windows breaks
|
|
|
|
++p;
|
|
|
|
}
|
|
|
|
else if (*p == L'\n') // Unix breaks
|
|
|
|
{
|
|
|
|
lineBreak = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Kerning.
|
|
|
|
core::vector2di k = getKerning(*p, previousChar);
|
|
|
|
thisLine.Width += k.X;
|
|
|
|
previousChar = *p;
|
|
|
|
|
|
|
|
// Check for linebreak.
|
|
|
|
if (lineBreak)
|
|
|
|
{
|
|
|
|
previousChar = 0;
|
|
|
|
dim.Height += thisLine.Height;
|
|
|
|
if (dim.Width < thisLine.Width)
|
|
|
|
dim.Width = thisLine.Width;
|
|
|
|
thisLine.Width = 0;
|
|
|
|
thisLine.Height = max_font_height;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
thisLine.Width += getWidthFromCharacter(*p);
|
|
|
|
}
|
|
|
|
if (dim.Width < thisLine.Width)
|
|
|
|
dim.Width = thisLine.Width;
|
|
|
|
|
|
|
|
return dim;
|
2010-04-23 01:13:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
inline u32 CGUITTFont::getWidthFromCharacter(wchar_t c) const
|
|
|
|
{
|
2019-03-07 10:23:54 -08:00
|
|
|
if (tt_face == nullptr || tt_face->face == nullptr) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
u32 n = getGlyphByChar(c);
|
|
|
|
if (n > 0)
|
|
|
|
{
|
|
|
|
int w = Glyphs[n - 1].bitmap.width;
|
|
|
|
s32 left = Glyphs[n - 1].bitmap.left;
|
|
|
|
if (w + left > 0) return w + left;
|
|
|
|
}
|
|
|
|
if (c >= 0x2000)
|
|
|
|
return Glyphs[0].size;
|
|
|
|
else return Glyphs[0].size / 2;
|
2010-04-23 01:13:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
inline u32 CGUITTFont::getHeightFromCharacter(wchar_t c) const
|
|
|
|
{
|
2019-03-07 10:23:54 -08:00
|
|
|
u32 n = getGlyphByChar(c);
|
|
|
|
if (Glyphs.size() < 1 || Glyphs.size() <= (n-1))
|
|
|
|
return 0;
|
|
|
|
if (n > 0)
|
|
|
|
{
|
|
|
|
// Grab the true height of the character, taking into account underhanging glyphs.
|
|
|
|
s32 height = Glyphs[n-1].size - Glyphs[n-1].bitmap.top + Glyphs[n-1].bitmap.height;
|
|
|
|
return height;
|
|
|
|
}
|
|
|
|
if (c >= 0x2000)
|
|
|
|
return Glyphs[0].size;
|
|
|
|
else return Glyphs[0].size / 2;
|
2010-04-23 01:13:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
u32 CGUITTFont::getGlyphByChar(wchar_t c) const
|
|
|
|
{
|
2019-03-07 10:23:54 -08:00
|
|
|
//assert(tt_face->face != nullptr);
|
|
|
|
//assert(tt_face->face != nullptr);
|
|
|
|
if (tt_face == nullptr || tt_face->face == nullptr) {
|
|
|
|
return 0; // 0 is bad by convention--existing code already checks for 0
|
|
|
|
}
|
|
|
|
u32 idx = FT_Get_Char_Index(tt_face->face, (FT_ULong)c);
|
|
|
|
|
|
|
|
// If the glyph hasn't been loaded yet, do it now.
|
|
|
|
if (idx && !Glyphs[idx - 1].cached)
|
|
|
|
Glyphs[idx - 1].cache(idx, FontHinting, AutoHinting);
|
|
|
|
|
|
|
|
return idx;
|
2010-04-23 01:13:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
s32 CGUITTFont::getCharacterFromPos(const wchar_t* text, s32 pixel_x) const
|
|
|
|
{
|
2019-03-07 10:23:54 -08:00
|
|
|
s32 x = 0;
|
|
|
|
s32 idx = 0;
|
2010-04-23 01:13:44 -07:00
|
|
|
|
2019-03-07 10:23:54 -08:00
|
|
|
while (text[idx])
|
|
|
|
{
|
|
|
|
x += getWidthFromCharacter(text[idx]);
|
2010-04-23 01:13:44 -07:00
|
|
|
|
2019-03-07 10:23:54 -08:00
|
|
|
if (x >= pixel_x)
|
|
|
|
return idx;
|
2010-04-23 01:13:44 -07:00
|
|
|
|
2019-03-07 10:23:54 -08:00
|
|
|
++idx;
|
|
|
|
}
|
2010-04-23 01:13:44 -07:00
|
|
|
|
2019-03-07 10:23:54 -08:00
|
|
|
return -1;
|
2010-04-23 01:13:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void CGUITTFont::setKerningWidth(s32 kerning)
|
|
|
|
{
|
2019-03-07 10:23:54 -08:00
|
|
|
GlobalKerningWidth = kerning;
|
2010-04-23 01:13:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
void CGUITTFont::setKerningHeight(s32 kerning)
|
|
|
|
{
|
2019-03-07 10:23:54 -08:00
|
|
|
GlobalKerningHeight = kerning;
|
2010-04-23 01:13:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
s32 CGUITTFont::getKerningWidth(const wchar_t* thisLetter, const wchar_t* previousLetter) const
|
|
|
|
{
|
2019-03-07 10:23:54 -08:00
|
|
|
if (tt_face == nullptr || tt_face->face == nullptr)
|
|
|
|
return GlobalKerningWidth;
|
|
|
|
if (thisLetter == nullptr || previousLetter == nullptr)
|
|
|
|
return 0;
|
2010-04-23 01:13:44 -07:00
|
|
|
|
2019-03-07 10:23:54 -08:00
|
|
|
// Return only the kerning width.
|
|
|
|
return getKerning(*thisLetter, *previousLetter).X;
|
2010-04-23 01:13:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
s32 CGUITTFont::getKerningHeight() const
|
|
|
|
{
|
2019-03-07 10:23:54 -08:00
|
|
|
// FreeType 2 currently doesn't return any height kerning information.
|
|
|
|
return GlobalKerningHeight;
|
2010-04-23 01:13:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
core::vector2di CGUITTFont::getKerning(const wchar_t thisLetter, const wchar_t previousLetter) const
|
|
|
|
{
|
2019-03-07 10:23:54 -08:00
|
|
|
if (tt_face == nullptr || thisLetter == 0 || previousLetter == 0)
|
|
|
|
return core::vector2di();
|
|
|
|
|
|
|
|
core::vector2di ret(GlobalKerningWidth, GlobalKerningHeight);
|
|
|
|
|
|
|
|
// Grab the face's flags to determine if the font is scalable or not.
|
|
|
|
FT_Long flags = tt_face->face->face_flags;
|
|
|
|
bool scalable = ((flags & FT_FACE_FLAG_SCALABLE) != 0);
|
|
|
|
|
|
|
|
// Get the kerning information.
|
|
|
|
FT_Vector v;
|
|
|
|
FT_Get_Kerning(tt_face->face, getGlyphByChar(previousLetter), getGlyphByChar(thisLetter), FT_KERNING_DEFAULT, &v);
|
|
|
|
|
|
|
|
// If we have a scalable font, the return value will be in font points.
|
|
|
|
if (scalable)
|
|
|
|
{
|
|
|
|
// Font points, so divide by 64.
|
|
|
|
ret.X += (v.x / 64);
|
|
|
|
ret.Y += (v.y / 64);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Pixel units.
|
|
|
|
ret.X += v.x;
|
|
|
|
ret.Y += v.y;
|
|
|
|
}
|
|
|
|
return ret;
|
2010-04-23 01:13:44 -07:00
|
|
|
}
|
|
|
|
|
2019-04-11 01:31:04 -07:00
|
|
|
void CGUITTFont::setInvisibleCharacters(const wchar_t *s)
|
2010-04-23 01:13:44 -07:00
|
|
|
{
|
2019-03-07 10:23:54 -08:00
|
|
|
Invisible = s;
|
2010-04-23 01:13:44 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
} // end namespace gui
|
|
|
|
} // end namespace irr
|