forked from Mirrors/openclonk
1276 lines
42 KiB
C++
1276 lines
42 KiB
C++
/*
|
|
* OpenClonk, http://www.openclonk.org
|
|
*
|
|
* Copyright (c) 2003-2009, RedWolf Design GmbH, http://www.clonk.de
|
|
*
|
|
* Portions might be copyrighted by other authors who have contributed
|
|
* to OpenClonk.
|
|
*
|
|
* Permission to use, copy, modify, and/or distribute this software for any
|
|
* purpose with or without fee is hereby granted, provided that the above
|
|
* copyright notice and this permission notice appear in all copies.
|
|
* See isc_license.txt for full license and disclaimer.
|
|
*
|
|
* "Clonk" is a registered trademark of Matthes Bender.
|
|
* See clonk_trademark_license.txt for full license.
|
|
*/
|
|
|
|
// text drawing facility for CStdDDraw
|
|
|
|
#include <Standard.h>
|
|
#include <StdBuf.h>
|
|
#include <StdDDraw2.h>
|
|
#include <StdSurface2.h>
|
|
#include <StdMarkup.h>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
|
|
#ifdef _WIN32
|
|
#include <tchar.h>
|
|
#include <stdio.h>
|
|
#else
|
|
#define _T(x) x
|
|
#endif // _WIN32
|
|
|
|
#ifdef HAVE_FREETYPE
|
|
#include <ft2build.h>
|
|
#include FT_FREETYPE_H
|
|
#endif // HAVE_FREETYPE
|
|
|
|
#ifdef HAVE_ICONV
|
|
#include <iconv.h>
|
|
#endif // HAVE_ICONV
|
|
|
|
|
|
/* Initialization */
|
|
|
|
#ifdef HAVE_FREETYPE
|
|
class CStdVectorFont
|
|
{
|
|
FT_Library library;
|
|
FT_Face face;
|
|
public:
|
|
CStdVectorFont(const char * filepathname) {
|
|
// Initialize Freetype
|
|
if (FT_Init_FreeType(&library))
|
|
throw std::runtime_error("Cannot init Freetype");
|
|
// Load the font
|
|
FT_Error e;
|
|
if (e=FT_New_Face(library, filepathname, 0, &face))
|
|
throw std::runtime_error(std::string("Cannot load ") + filepathname + ": " + FormatString("%d",e).getData());
|
|
}
|
|
CStdVectorFont(const StdBuf & Data) {
|
|
// Initialize Freetype
|
|
if (FT_Init_FreeType(&library))
|
|
throw std::runtime_error("Cannot init Freetype");
|
|
// Load the font
|
|
FT_Error e;
|
|
if (e=FT_New_Memory_Face(library, static_cast<const FT_Byte *>(Data.getData()), Data.getSize(), 0, &face))
|
|
throw std::runtime_error(std::string("Cannot load font: ") + FormatString("%d",e).getData());
|
|
}
|
|
~CStdVectorFont() {
|
|
FT_Done_Face(face);
|
|
FT_Done_FreeType(library);
|
|
}
|
|
operator FT_Face () { return face; }
|
|
FT_Face operator -> () { return face; }
|
|
};
|
|
|
|
CStdVectorFont * CStdFont::CreateFont(const char *szFaceName)
|
|
{
|
|
return new CStdVectorFont(szFaceName);
|
|
}
|
|
CStdVectorFont * CStdFont::CreateFont(const StdBuf & Data)
|
|
{
|
|
return new CStdVectorFont(Data);
|
|
}
|
|
void CStdFont::DestroyFont(CStdVectorFont * pFont)
|
|
{
|
|
delete pFont;
|
|
}
|
|
#elif (defined _WIN32)
|
|
class CStdVectorFont
|
|
{
|
|
private:
|
|
StdStrBuf sFontName;
|
|
public:
|
|
CStdVectorFont(const char * name)
|
|
{
|
|
sFontName.Copy(name);
|
|
}
|
|
const char *GetFontName() { return sFontName.getData(); }
|
|
};
|
|
CStdVectorFont * CStdFont::CreateFont(const char *szFaceName)
|
|
{
|
|
return new CStdVectorFont(szFaceName);
|
|
}
|
|
void CStdFont::DestroyFont(CStdVectorFont * pFont)
|
|
{
|
|
delete pFont;
|
|
}
|
|
#else
|
|
CStdVectorFont * CStdFont::CreateFont(const StdBuf & Data)
|
|
{
|
|
return 0;
|
|
}
|
|
CStdVectorFont * CStdFont::CreateFont(const char *szFaceName)
|
|
{
|
|
return 0;
|
|
}
|
|
void CStdFont::DestroyFont(CStdVectorFont * pFont)
|
|
{
|
|
}
|
|
#endif
|
|
|
|
CStdFont::CStdFont()
|
|
{
|
|
// set default values
|
|
psfcFontData = NULL;
|
|
sfcCurrent = NULL;
|
|
iNumFontSfcs = 0;
|
|
iSfcSizes = 64;
|
|
dwDefFontHeight=iLineHgt=10;
|
|
iFontZoom=1; // default: no internal font zooming - likely no antialiasing either...
|
|
iHSpace=-1;
|
|
iGfxLineHgt=iLineHgt+1;
|
|
dwWeight=FW_NORMAL;
|
|
fDoShadow=false;
|
|
fUTF8 = false;
|
|
// font not yet initialized
|
|
*szFontName=0;
|
|
id=0;
|
|
pCustomImages=NULL;
|
|
fPrerenderedFont = false;
|
|
#if defined _WIN32 && !(defined HAVE_FREETYPE)
|
|
hDC = NULL;
|
|
hbmBitmap = NULL;
|
|
hFont = NULL;
|
|
#elif (defined HAVE_FREETYPE)
|
|
pVectorFont = NULL;
|
|
#endif
|
|
}
|
|
|
|
bool CStdFont::AddSurface()
|
|
{
|
|
// add new surface as render target; copy old ones
|
|
CSurface **pNewSfcs = new CSurface *[iNumFontSfcs+1];
|
|
if (iNumFontSfcs) memcpy(pNewSfcs, psfcFontData, iNumFontSfcs * sizeof (CSurface *));
|
|
delete [] psfcFontData;
|
|
psfcFontData = pNewSfcs;
|
|
CSurface *sfcNew = psfcFontData[iNumFontSfcs] = new CSurface();
|
|
++iNumFontSfcs;
|
|
if (iSfcSizes) if (!sfcNew->Create(iSfcSizes, iSfcSizes)) return false;
|
|
sfcCurrent = sfcNew;
|
|
iCurrentSfcX = iCurrentSfcY = 0;
|
|
return true;
|
|
}
|
|
|
|
bool CStdFont::CheckRenderedCharSpace(uint32_t iCharWdt, uint32_t iCharHgt)
|
|
{
|
|
// need to do a line break?
|
|
if (iCurrentSfcX + iCharWdt >= (uint32_t)iSfcSizes) if (iCurrentSfcX)
|
|
{
|
|
iCurrentSfcX = 0;
|
|
iCurrentSfcY += iCharHgt;
|
|
if (iCurrentSfcY + iCharHgt >= (uint32_t)iSfcSizes)
|
|
{
|
|
// surface is full: Next one
|
|
if (!AddSurface()) return false;
|
|
}
|
|
}
|
|
// OK draw it there
|
|
return true;
|
|
}
|
|
|
|
bool CStdFont::AddRenderedChar(uint32_t dwChar, CFacet *pfctTarget)
|
|
{
|
|
#if defined _WIN32 && !(defined HAVE_FREETYPE) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
// Win32-API character rendering
|
|
// safety
|
|
if (fPrerenderedFont || !sfcCurrent) return false;
|
|
bool fUnicode = (dwChar >= 256);
|
|
char str[2] = _T("x");
|
|
wchar_t wstr[2] = L"x";
|
|
SIZE size;
|
|
if (fUnicode)
|
|
{
|
|
wstr[0] = dwChar;
|
|
GetTextExtentPoint32W( hDC, wstr, 1, &size );
|
|
}
|
|
else
|
|
{
|
|
// set character
|
|
str[0] = dwChar;
|
|
// get size
|
|
GetTextExtentPoint32( hDC, str, 1, &size );
|
|
}
|
|
// keep text shadow in mind
|
|
if (fDoShadow) { ++size.cx; ++size.cy; }
|
|
// adjust line height to max character height
|
|
if (!fUnicode) iLineHgt=Max<int>(iLineHgt, size.cy+1);
|
|
// print character on empty surface
|
|
ZeroMemory(pBitmapBits, iBitmapSize*iBitmapSize*4);
|
|
if (fUnicode)
|
|
ExtTextOutW( hDC, 0, 0, ETO_OPAQUE, NULL, wstr, 1, NULL );
|
|
else
|
|
ExtTextOut( hDC, 0, 0, ETO_OPAQUE, NULL, str, 1, NULL );
|
|
// must not overflow surfaces: do some size bounds
|
|
size.cx = Min<int>(size.cx, Min<int>(iSfcSizes, iBitmapSize));
|
|
size.cy = Min<int>(size.cy, Min<int>(iSfcSizes, iBitmapSize));
|
|
// need to do a line break or new surface?
|
|
if (!CheckRenderedCharSpace(size.cx, size.cy)) return false;
|
|
// transfer bitmap data into alpha channel of surface
|
|
if (!sfcCurrent->Lock()) return false;
|
|
for (int y=0; y<size.cy; ++y) for (int x=0; x<size.cx; ++x)
|
|
{
|
|
// get value; determine shadow value by pos moved 1px to upper left
|
|
BYTE bAlpha = 255 - (BYTE)(pBitmapBits[iBitmapSize*y + x] & 0xff);
|
|
BYTE bAlphaShadow;
|
|
if (x&&y && fDoShadow)
|
|
bAlphaShadow = 255 - (BYTE)((pBitmapBits[iBitmapSize*(y-1) + x-1] & 0xff)*1/1);
|
|
else
|
|
bAlphaShadow = 255;
|
|
// calc pixel value: white char on black shadow (if shadow is desired)
|
|
DWORD dwPixVal = bAlphaShadow << 24;
|
|
BltAlpha(dwPixVal, bAlpha << 24 | 0xffffff);
|
|
sfcCurrent->SetPixDw(iCurrentSfcX+x,iCurrentSfcY+y,dwPixVal);
|
|
}
|
|
sfcCurrent->Unlock();
|
|
// set texture coordinates
|
|
pfctTarget->Set(sfcCurrent, iCurrentSfcX, iCurrentSfcY, size.cx, size.cy);
|
|
|
|
#elif defined HAVE_FREETYPE // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
// Freetype character rendering
|
|
FT_Set_Pixel_Sizes(*pVectorFont, dwDefFontHeight, dwDefFontHeight);
|
|
int32_t iBoldness = dwWeight-400; // zero is normal; 300 is bold
|
|
if (iBoldness)
|
|
{
|
|
iBoldness = (1<<16) + (iBoldness<<16)/400;
|
|
FT_Matrix mat;
|
|
mat.xx = iBoldness; mat.xy = mat.yx = 0; mat.yy = 1<<16;
|
|
//.*(100 + iBoldness/3)/100
|
|
FT_Set_Transform(*pVectorFont, &mat, NULL);
|
|
}
|
|
else
|
|
{
|
|
FT_Set_Transform(*pVectorFont, NULL, NULL);
|
|
}
|
|
// Render
|
|
if (FT_Load_Char(*pVectorFont, dwChar, FT_LOAD_RENDER | FT_LOAD_NO_HINTING))
|
|
{
|
|
// although the character was not drawn, assume it's not in the font and won't be needed
|
|
// so return success here
|
|
return true;
|
|
}
|
|
// Make a shortcut to the glyph
|
|
FT_GlyphSlot slot = (*pVectorFont)->glyph;
|
|
if (slot->bitmap.pixel_mode != FT_PIXEL_MODE_GRAY)
|
|
{
|
|
// although the character was drawn in a strange way, assume it's not in the font and won't be needed
|
|
// so return success here
|
|
return true;
|
|
}
|
|
// linebreak/ new surface check
|
|
int width = Max<int>(slot->advance.x / 64, Max(slot->bitmap_left,0) + slot->bitmap.width) + fDoShadow;
|
|
if (!CheckRenderedCharSpace(width, iGfxLineHgt)) return false;
|
|
// offset from the top
|
|
int at_y = iCurrentSfcY + dwDefFontHeight * (*pVectorFont)->ascender / (*pVectorFont)->units_per_EM - slot->bitmap_top;
|
|
int at_x = iCurrentSfcX + Max(slot->bitmap_left,0);
|
|
// Copy to the surface
|
|
if (!sfcCurrent->Lock()) return false;
|
|
for(int y = 0; y < slot->bitmap.rows + fDoShadow; ++y)
|
|
{
|
|
for(int x = 0; x < slot->bitmap.width + fDoShadow; ++x)
|
|
{
|
|
unsigned char bAlpha, bAlphaShadow;
|
|
if (x < slot->bitmap.width && y < slot->bitmap.rows)
|
|
bAlpha = 255 - (unsigned char)(slot->bitmap.buffer[slot->bitmap.width * y + x]);
|
|
else
|
|
bAlpha = 255;
|
|
// Make a shadow from the upper-left pixel, and blur with the eight neighbors
|
|
DWORD dwPixVal = 0u;
|
|
bAlphaShadow = 255;
|
|
if ((x || y) && fDoShadow)
|
|
{
|
|
int iShadow = 0;
|
|
if (x < slot->bitmap.width && y < slot->bitmap.rows) iShadow += slot->bitmap.buffer[(x - 0) + slot->bitmap.width * (y - 0)];
|
|
if (x > 1 && y < slot->bitmap.rows) iShadow += slot->bitmap.buffer[(x - 2) + slot->bitmap.width * (y - 0)];
|
|
if (x > 0 && y < slot->bitmap.rows) iShadow += slot->bitmap.buffer[(x - 1) + slot->bitmap.width * (y - 0)];
|
|
if (x < slot->bitmap.width && y > 1 ) iShadow += slot->bitmap.buffer[(x - 0) + slot->bitmap.width * (y - 2)];
|
|
if (x > 1 && y > 1 ) iShadow += slot->bitmap.buffer[(x - 2) + slot->bitmap.width * (y - 2)];
|
|
if (x > 0 && y > 1 ) iShadow += slot->bitmap.buffer[(x - 1) + slot->bitmap.width * (y - 2)];
|
|
if (x < slot->bitmap.width && y > 0 ) iShadow += slot->bitmap.buffer[(x - 0) + slot->bitmap.width * (y - 1)];
|
|
if (x > 1 && y > 0 ) iShadow += slot->bitmap.buffer[(x - 2) + slot->bitmap.width * (y - 1)];
|
|
if (x > 0 && y > 0 ) iShadow += slot->bitmap.buffer[(x - 1) + slot->bitmap.width * (y - 1)]*8;
|
|
bAlphaShadow -= iShadow / 16;
|
|
// because blitting on a black pixel reduces luminosity as compared to shadowless font,
|
|
// assume luminosity as if blitting shadowless font on a 50% gray background
|
|
unsigned char cBack = (255-bAlpha);
|
|
dwPixVal = RGB(cBack/2, cBack/2, cBack/2);
|
|
}
|
|
dwPixVal += bAlphaShadow << 24;
|
|
BltAlpha(dwPixVal, bAlpha << 24 | 0xffffff);
|
|
sfcCurrent->SetPixDw(at_x + x, at_y + y, dwPixVal);
|
|
}
|
|
}
|
|
sfcCurrent->Unlock();
|
|
// Save the position of the glyph for the rendering code
|
|
pfctTarget->Set(sfcCurrent, iCurrentSfcX, iCurrentSfcY, width, iGfxLineHgt);
|
|
#endif // end of freetype rendering - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
|
|
|
// advance texture position
|
|
iCurrentSfcX += pfctTarget->Wdt;
|
|
return true;
|
|
}
|
|
|
|
uint32_t CStdFont::GetNextUTF8Character(const char **pszString)
|
|
{
|
|
// assume the current character is UTF8 already (i.e., highest bit set)
|
|
const char *szString = *pszString;
|
|
unsigned char c = *szString++;
|
|
uint32_t dwResult = '?';
|
|
assert(c>127);
|
|
if (c>191 && c<224)
|
|
{
|
|
unsigned char c2 = *szString++;
|
|
if ((c2 & 192) != 128) { *pszString = szString; return '?'; }
|
|
dwResult = (int(c&31)<<6) | (c2&63); // two char code
|
|
}
|
|
else if (c >= 224 && c <= 239)
|
|
{
|
|
unsigned char c2 = *szString++;
|
|
if ((c2 & 192) != 128) { *pszString = szString; return '?'; }
|
|
unsigned char c3 = *szString++;
|
|
if ((c3 & 192) != 128) { *pszString = szString; return '?'; }
|
|
dwResult = (int(c&15)<<12) | (int(c2&63)<<6) | int(c3&63); // three char code
|
|
}
|
|
else if (c >= 240 && c <= 247)
|
|
{
|
|
unsigned char c2 = *szString++;
|
|
if ((c2 & 192) != 128) { *pszString = szString; return '?'; }
|
|
unsigned char c3 = *szString++;
|
|
if ((c3 & 192) != 128) { *pszString = szString; return '?'; }
|
|
unsigned char c4 = *szString++;
|
|
if ((c4 & 192) != 128) { *pszString = szString; return '?'; }
|
|
dwResult = (int(c&7)<<18) | (int(c2&63)<<12) | (int(c3&63)<<6) | int(c4&63); // four char code
|
|
}
|
|
*pszString = szString;
|
|
return dwResult;
|
|
}
|
|
|
|
CFacet &CStdFont::GetUnicodeCharacterFacet(uint32_t c)
|
|
{
|
|
// find/add facet in map
|
|
CFacet &rFacet = fctUnicodeMap[c];
|
|
// create character on the fly if necessary and possible
|
|
if (!rFacet.Surface && !fPrerenderedFont) AddRenderedChar(c, &rFacet);
|
|
// rendering might have failed, in which case rFacet remains empty. Should be OK; char won't be printed then
|
|
return rFacet;
|
|
}
|
|
|
|
void CStdFont::Init(CStdVectorFont & VectorFont, DWORD dwHeight, DWORD dwFontWeight, const char * szCharset, bool fDoShadow)
|
|
{
|
|
// clear previous
|
|
Clear();
|
|
// set values
|
|
iHSpace=fDoShadow ? -1 : 0; // horizontal shadow
|
|
dwWeight=dwFontWeight;
|
|
this->fDoShadow = fDoShadow;
|
|
if (SEqual(szCharset, "UTF-8")) fUTF8 = true;
|
|
// determine needed texture size
|
|
if (dwHeight * iFontZoom > 40)
|
|
iSfcSizes = 512;
|
|
else if (dwDefFontHeight * iFontZoom > 20)
|
|
iSfcSizes = 256;
|
|
else
|
|
iSfcSizes = 128;
|
|
dwDefFontHeight = dwHeight;
|
|
// create surface
|
|
if (!AddSurface())
|
|
{
|
|
Clear();
|
|
throw std::runtime_error(std::string("Cannot create surface (") + szFontName + ")");
|
|
}
|
|
|
|
#if defined _WIN32 && !(defined HAVE_FREETYPE)
|
|
// drawing using WinGDI
|
|
iLineHgt=dwHeight;
|
|
iGfxLineHgt=iLineHgt+fDoShadow; // vertical shadow
|
|
|
|
// prepare to create an offscreen bitmap to render into
|
|
iBitmapSize = DWordAligned(dwDefFontHeight * iFontZoom * 5);
|
|
BITMAPINFO bmi; ZeroMemory(&bmi.bmiHeader, sizeof(BITMAPINFOHEADER));
|
|
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
|
bmi.bmiHeader.biWidth = iBitmapSize;
|
|
bmi.bmiHeader.biHeight = -iBitmapSize;
|
|
bmi.bmiHeader.biPlanes = 1;
|
|
bmi.bmiHeader.biCompression = BI_RGB;
|
|
bmi.bmiHeader.biBitCount = 32;
|
|
|
|
// create a rendering DC and a bitmap for the font
|
|
hDC = CreateCompatibleDC(NULL);
|
|
if (!hDC) { Clear(); throw std::runtime_error(std::string("Cannot create DC (") + szFontName + ")"); }
|
|
hbmBitmap = CreateDIBSection( hDC, &bmi, DIB_RGB_COLORS,
|
|
(VOID**)&pBitmapBits, NULL, 0 );
|
|
if (!hbmBitmap) { Clear(); throw std::runtime_error(std::string("Cannot create DIBSection (") + szFontName + ")"); }
|
|
//SetMapMode(hDC, MM_TEXT);
|
|
char bCharset = GetCharsetCode(szCharset);
|
|
// create a font. try ClearType first...
|
|
const char *szFontName = VectorFont.GetFontName();
|
|
const char *szFontName2;
|
|
if (szFontName && *szFontName) szFontName2 = szFontName; else szFontName2 = "Comic Sans MS";
|
|
int iFontHeight = dwDefFontHeight * GetDeviceCaps(hDC, LOGPIXELSY) * iFontZoom / 72;
|
|
hFont = ::CreateFont(iFontHeight, 0, 0, 0, dwFontWeight, FALSE,
|
|
FALSE, FALSE, bCharset, OUT_DEFAULT_PRECIS,
|
|
CLIP_DEFAULT_PRECIS, 5,
|
|
VARIABLE_PITCH, szFontName2);
|
|
|
|
// ClearType failed: try antialiased (not guaranteed)
|
|
if (!hFont) hFont = ::CreateFont(iFontHeight, 0, 0, 0, dwFontWeight, FALSE,
|
|
FALSE, FALSE, bCharset, OUT_DEFAULT_PRECIS,
|
|
CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY,
|
|
VARIABLE_PITCH, szFontName2);
|
|
|
|
if (!hFont)
|
|
{
|
|
Clear();
|
|
throw std::runtime_error(std::string("Cannot create Font (") + szFontName + ")");
|
|
}
|
|
SelectObject( hDC, hbmBitmap );
|
|
SelectObject( hDC, hFont );
|
|
|
|
// set text properties
|
|
SetTextColor( hDC, RGB(255,255,255) );
|
|
SetBkColor( hDC, 0x00000000 );
|
|
SetTextAlign( hDC, TA_TOP );
|
|
|
|
// line height adjusted when characters are created
|
|
iLineHgt=0;
|
|
|
|
#elif (defined HAVE_FREETYPE)
|
|
|
|
// Store vector font - assumed to be held externally!
|
|
pVectorFont = &VectorFont;
|
|
// Get size
|
|
// FIXME: use bbox or dynamically determined line heights here
|
|
iLineHgt = (VectorFont->ascender - VectorFont->descender) * dwHeight / VectorFont->units_per_EM;
|
|
iGfxLineHgt = iLineHgt + fDoShadow; // vertical shadow
|
|
#else
|
|
throw std::runtime_error("You have a engine without Truetype support.");
|
|
#endif // HAVE_FREETYPE
|
|
|
|
// loop through all ANSI/ASCII printable characters and prepare them
|
|
// in case of UTF8, unicode characters will be created on the fly and extended ASCII characters (128-255) are not needed
|
|
// now render all characters!
|
|
|
|
#if defined HAVE_ICONV
|
|
// Initialize iconv
|
|
struct iconv_t_wrapper {
|
|
iconv_t i;
|
|
iconv_t_wrapper (const char * to, const char * from) {
|
|
i = iconv_open(to, from);
|
|
if (i == iconv_t(-1))
|
|
throw std::runtime_error(std::string("Cannot open iconv (") + to + ", " + from + ")");
|
|
}
|
|
~iconv_t_wrapper () { iconv_close (i); }
|
|
operator iconv_t () { return i; }
|
|
};
|
|
#ifdef __BIG_ENDIAN__
|
|
iconv_t_wrapper iconv_handle("UCS-4BE",GetCharsetCodeName(szCharset));
|
|
#else
|
|
iconv_t_wrapper iconv_handle("UCS-4LE",GetCharsetCodeName(szCharset));
|
|
#endif
|
|
#elif defined (_WIN32)
|
|
int32_t iCodePage = GetCharsetCodePage(szCharset);
|
|
#endif
|
|
int cMax = fUTF8 ? 127 : 255;
|
|
for (int c=' '; c <= cMax; ++c)
|
|
{
|
|
uint32_t dwChar = c;
|
|
#if defined HAVE_ICONV
|
|
// convert from whatever legacy encoding in use to unicode
|
|
if (!fUTF8)
|
|
{
|
|
// Convert to unicode
|
|
char chr = dwChar;
|
|
char * in = &chr;
|
|
char * out = reinterpret_cast<char *>(&dwChar);
|
|
size_t insize = 1;
|
|
size_t outsize = 4;
|
|
iconv(iconv_handle, const_cast<ICONV_CONST char * * >(&in), &insize, &out, &outsize);
|
|
}
|
|
#elif defined (_WIN32)
|
|
// convert using Win32 API
|
|
if (!fUTF8 && c>=128)
|
|
{
|
|
char cc[2] = { c, '\0' };
|
|
wchar_t outbuf[4];
|
|
if (MultiByteToWideChar(iCodePage, 0, cc, -1, outbuf, 4)) // 2do: Convert using proper codepage
|
|
{
|
|
// now convert from UTF-16 to UCS-4
|
|
if (((outbuf[0] & 0xfc00) == 0xd800) && ((outbuf[1] & 0xfc00) == 0xdc00))
|
|
{
|
|
dwChar = 0x10000 + (((outbuf[0] & 0x3ff) << 10) | (outbuf[1] & 0x3ff));
|
|
}
|
|
else
|
|
dwChar = outbuf[0];
|
|
}
|
|
else
|
|
{
|
|
// conversion error. Shouldn't be fatal; just pretend it were a Unicode character
|
|
}
|
|
}
|
|
#else
|
|
// no conversion available? Just break for non-iso8859-1.
|
|
#endif // defined HAVE_ICONV
|
|
if (!AddRenderedChar(dwChar, &(fctAsciiTexCoords[c-' '])))
|
|
{
|
|
Clear();
|
|
throw std::runtime_error(std::string("Cannot render characters for Font (") + szFontName + ")");
|
|
}
|
|
}
|
|
// adjust line height
|
|
iLineHgt /= iFontZoom;
|
|
// font successfully created; set name
|
|
//SCopy(szFontName2, this->szFontName, 80);
|
|
fPrerenderedFont = false;
|
|
if (0) for (int i = 0; i < iNumFontSfcs; ++i)
|
|
{
|
|
StdStrBuf pngfilename = FormatString("%s%i%s_%d.png",szFontName,dwHeight,fDoShadow ? "_shadow" : "",i);
|
|
psfcFontData[i]->SavePNG(pngfilename.getData(), true, false, false);
|
|
}
|
|
}
|
|
|
|
const DWORD FontDelimeterColor = 0xff0000,
|
|
FontDelimiterColorLB = 0x00ff00,
|
|
FontDelimeterColorIndent1 = 0xffff00,
|
|
FontDelimeterColorIndent2 = 0xff00ff;
|
|
|
|
// perform color matching in 16 bit
|
|
inline bool ColorMatch(DWORD dwClr1, DWORD dwClr2)
|
|
{ return ClrDw2W(dwClr1) == ClrDw2W(dwClr2); }
|
|
|
|
void CStdFont::Init(const char *szFontName, CSurface *psfcFontSfc, int iIndent)
|
|
{
|
|
// clear previous
|
|
Clear();
|
|
// grab surface
|
|
iSfcSizes = 0;
|
|
if (!AddSurface()) { Clear(); throw std::runtime_error(std::string("Error creating surface for ") + szFontName); }
|
|
sfcCurrent->MoveFrom(psfcFontSfc);
|
|
// extract character positions from image data
|
|
if (!sfcCurrent->Hgt || !sfcCurrent->Lock())
|
|
{
|
|
Clear();
|
|
throw std::runtime_error(std::string("Error loading ") + szFontName);
|
|
}
|
|
// get line height
|
|
iGfxLineHgt=1;
|
|
while (iGfxLineHgt<sfcCurrent->Hgt)
|
|
{
|
|
DWORD dwPix = sfcCurrent->GetPixDw(0, iGfxLineHgt, false);
|
|
if (ColorMatch(dwPix, FontDelimeterColor) || ColorMatch(dwPix, FontDelimiterColorLB) ||
|
|
ColorMatch(dwPix, FontDelimeterColorIndent1) || ColorMatch(dwPix, FontDelimeterColorIndent2))
|
|
break;
|
|
++iGfxLineHgt;
|
|
}
|
|
// set font height and width indent
|
|
dwDefFontHeight=iLineHgt=iGfxLineHgt-iIndent;
|
|
iHSpace=-iIndent;
|
|
// determine character sizes
|
|
int iX=0, iY=0;
|
|
for (int c=' '; c < 256; ++c)
|
|
{
|
|
// save character pos
|
|
fctAsciiTexCoords[c-' '].X = iX; // left
|
|
fctAsciiTexCoords[c-' '].Y = iY; // top
|
|
bool IsLB=false;
|
|
// get horizontal extent
|
|
while (iX < sfcCurrent->Wdt)
|
|
{
|
|
DWORD dwPix = sfcCurrent->GetPixDw(iX, iY, false);
|
|
if (ColorMatch(dwPix, FontDelimeterColor) || ColorMatch(dwPix, FontDelimeterColorIndent1) || ColorMatch(dwPix, FontDelimeterColorIndent2))
|
|
break;
|
|
if (ColorMatch(dwPix, FontDelimiterColorLB)) { IsLB=true; break; }
|
|
++iX;
|
|
}
|
|
// remove vertical line
|
|
if (iX < sfcCurrent->Wdt)
|
|
for (int y=0; y<iGfxLineHgt; ++y)
|
|
sfcCurrent->SetPixDw(iX, iY+y, 0xffffffff);
|
|
// save char size
|
|
fctAsciiTexCoords[c-' '].Wdt = iX - fctAsciiTexCoords[c-' '].X;
|
|
fctAsciiTexCoords[c-' '].Hgt = iGfxLineHgt;
|
|
// next line?
|
|
if (++iX >= sfcCurrent->Wdt || IsLB)
|
|
{
|
|
iY += iGfxLineHgt;
|
|
iX = 0;
|
|
// remove horizontal line
|
|
if (iY < sfcCurrent->Hgt)
|
|
for (int x=0; x<sfcCurrent->Wdt; ++x)
|
|
sfcCurrent->SetPixDw(x, iY, 0xffffffff);
|
|
// skip empty line
|
|
++iY;
|
|
// end reached?
|
|
if (iY+iGfxLineHgt > sfcCurrent->Hgt)
|
|
{
|
|
// all filled
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// release texture data
|
|
sfcCurrent->Unlock();
|
|
// adjust line height
|
|
iLineHgt /= iFontZoom;
|
|
// set name
|
|
SCopy(szFontName, this->szFontName);
|
|
// mark prerendered
|
|
fPrerenderedFont = true;
|
|
}
|
|
|
|
void CStdFont::Clear()
|
|
{
|
|
#if defined _WIN32 && !(defined HAVE_FREETYPE)
|
|
// clear Win32API font stuff
|
|
if (hbmBitmap) { DeleteObject(hbmBitmap); hbmBitmap = NULL; }
|
|
if (hDC) { DeleteDC(hDC); hDC = NULL; }
|
|
if (hFont) { DeleteObject(hFont); hDC = NULL; }
|
|
#elif (defined HAVE_FREETYPE)
|
|
pVectorFont = NULL;
|
|
#endif
|
|
// clear font sfcs
|
|
if (psfcFontData)
|
|
{
|
|
while (iNumFontSfcs--) delete psfcFontData[iNumFontSfcs];
|
|
delete [] psfcFontData;
|
|
psfcFontData = NULL;
|
|
}
|
|
sfcCurrent = NULL;
|
|
iNumFontSfcs = 0;
|
|
for (int c=' '; c<256; ++c) fctAsciiTexCoords[c-' '].Clear();
|
|
fctUnicodeMap.clear();
|
|
// set default values
|
|
dwDefFontHeight=iLineHgt=10;
|
|
iFontZoom=1; // default: no internal font zooming - likely no antialiasing either...
|
|
iHSpace=-1;
|
|
iGfxLineHgt=iLineHgt+1;
|
|
dwWeight=FW_NORMAL;
|
|
fDoShadow=false;
|
|
fPrerenderedFont = false;
|
|
fUTF8 = false;
|
|
// font not yet initialized
|
|
*szFontName=0;
|
|
id=0;
|
|
}
|
|
|
|
|
|
|
|
/* Text size measurement */
|
|
|
|
|
|
bool CStdFont::GetTextExtent(const char *szText, int32_t &rsx, int32_t &rsy, bool fCheckMarkup)
|
|
{
|
|
// safety
|
|
if (!szText) return false;
|
|
// keep track of each row's size
|
|
int iRowWdt=0,iWdt=0,iHgt=iLineHgt;
|
|
// ignore any markup
|
|
CMarkup MarkupChecker(false);
|
|
// go through all text
|
|
while (*szText)
|
|
{
|
|
// ignore markup
|
|
if (fCheckMarkup) MarkupChecker.SkipTags(&szText);
|
|
// get current char
|
|
uint32_t c = GetNextCharacter(&szText);
|
|
// done? (must check here, because markup-skip may have led to text end)
|
|
if (!c) break;
|
|
// line break?
|
|
if( c == _T('\n') || (fCheckMarkup && c == _T('|'))) { iRowWdt=0; iHgt+=iLineHgt; continue; }
|
|
// ignore system characters
|
|
if( c < _T(' ') ) continue;
|
|
// image?
|
|
int iImgLgt;
|
|
if (fCheckMarkup && c=='{' && szText[0]=='{' && szText[1]!='{' && (iImgLgt=SCharPos('}', szText+1))>0 && szText[iImgLgt+2]=='}')
|
|
{
|
|
char imgbuf[101];
|
|
SCopy(szText+1, imgbuf, Min(iImgLgt, 100));
|
|
CFacet fct;
|
|
// image renderer initialized?
|
|
if (pCustomImages)
|
|
// try to get an image then
|
|
pCustomImages->GetFontImage(imgbuf, fct);
|
|
if (fct.Hgt)
|
|
{
|
|
// image found: adjust aspect by font height and calc appropriate width
|
|
iRowWdt += (fct.Wdt * iGfxLineHgt) / fct.Hgt;
|
|
}
|
|
else
|
|
{
|
|
// image renderer not hooked or ID not found, or surface not present: just ignore it
|
|
// printing it out wouldn't look better...
|
|
}
|
|
// skip image tag
|
|
szText+=iImgLgt+3;
|
|
}
|
|
else
|
|
{
|
|
// regular char
|
|
// look up character width in texture coordinates table
|
|
iRowWdt += GetCharacterFacet(c).Wdt / iFontZoom;
|
|
}
|
|
// apply horizontal indent for all but last char
|
|
if (*szText) iRowWdt += iHSpace;
|
|
// adjust max row size
|
|
if (iRowWdt>iWdt) iWdt=iRowWdt;
|
|
}
|
|
// store output
|
|
rsx=iWdt; rsy=iHgt;
|
|
// done, success
|
|
return true;
|
|
}
|
|
|
|
int CStdFont::BreakMessage(const char *szMsg, int iWdt, char *szOut, int iMaxOutLen, bool fCheckMarkup, float fZoom)
|
|
{
|
|
// 2do: Implement this in terms of StdStrBuf-version
|
|
// note iMaxOutLen does not include terminating null character
|
|
// safety
|
|
if (!iMaxOutLen) return 0;
|
|
if (!szMsg)
|
|
{
|
|
if (szOut) *szOut=0;
|
|
return 0;
|
|
}
|
|
|
|
uint32_t c;
|
|
const char *szPos=szMsg, // current parse position in the text
|
|
*szLastBreakPos = szMsg, // points to the char after at (whitespace) or after ('-') which text can be broken
|
|
*szLastEmergenyBreakPos, // same, but at last char in case no suitable linebreak could be found
|
|
*szLastPos; // last position until which buffer has been transferred to output
|
|
char *szLastBreakOut, *szLastEmergencyBreakOut; // position of output pointer at break positions
|
|
int iX=0, // current text width at parse pos
|
|
iXBreak=0, // text width as it was at last break pos
|
|
iXEmergencyBreak, // same, but at last char in case no suitable linebreak could be found
|
|
iHgt=iLineHgt; // total height of output text
|
|
bool fIsFirstLineChar = true;
|
|
// ignore any markup
|
|
CMarkup MarkupChecker(false);
|
|
// go through all text
|
|
while (*(szLastPos = szPos))
|
|
{
|
|
// ignore markup
|
|
if (fCheckMarkup) MarkupChecker.SkipTags(&szPos);
|
|
// get current char
|
|
c = GetNextCharacter(&szPos);
|
|
// done? (must check here, because markup-skip may have led to text end)
|
|
if (!c) break;
|
|
// manual break?
|
|
int iCharWdt = 0;
|
|
if (c != '\n' && (!fCheckMarkup || c != '|'))
|
|
{
|
|
// image?
|
|
int iImgLgt;
|
|
if (fCheckMarkup && c=='{' && szPos[0]=='{' && szPos[1]!='{' && (iImgLgt=SCharPos('}', szPos+1))>0 && szPos[iImgLgt+2]=='}')
|
|
{
|
|
char imgbuf[101];
|
|
SCopy(szPos+1, imgbuf, Min(iImgLgt, 100));
|
|
CFacet fct;
|
|
// image renderer initialized?
|
|
if (pCustomImages)
|
|
// try to get an image then
|
|
pCustomImages->GetFontImage(imgbuf, fct);
|
|
if (fct.Hgt)
|
|
{
|
|
// image found: adjust aspect by font height and calc appropriate width
|
|
iCharWdt = (fct.Wdt * iGfxLineHgt) / fct.Hgt;
|
|
}
|
|
else
|
|
{
|
|
// image renderer not hooked or ID not found, or surface not present: just ignore it
|
|
// printing it out wouldn't look better...
|
|
iCharWdt = 0;
|
|
}
|
|
// skip image tag
|
|
szPos+=iImgLgt+3;
|
|
}
|
|
else
|
|
{
|
|
// regular char
|
|
// look up character width in texture coordinates table
|
|
if (c >= ' ')
|
|
iCharWdt = int(fZoom * GetCharacterFacet(c).Wdt / iFontZoom) + iHSpace;
|
|
else
|
|
iCharWdt = 0; // OMFG ctrl char
|
|
}
|
|
// add chars to output
|
|
while (szLastPos != szPos)
|
|
{
|
|
if (szOut)
|
|
*szOut++ = *szLastPos++;
|
|
else
|
|
++szLastPos;
|
|
if (szOut && !--iMaxOutLen)
|
|
{
|
|
// buffer end: cut and terminate
|
|
*szOut = '\0';
|
|
break;
|
|
}
|
|
}
|
|
if (szOut && !iMaxOutLen) break;
|
|
// add to line; always add one char at minimum
|
|
if ((iX+=iCharWdt) <= iWdt || fIsFirstLineChar)
|
|
{
|
|
// check whether linebreak possibility shall be marked here
|
|
// 2do: What about unicode-spaces?
|
|
if (c<256) if (isspace((unsigned char)c) || c == '-')
|
|
{
|
|
szLastBreakPos = szPos;
|
|
szLastBreakOut = szOut;
|
|
// space: Break directly at space if it isn't the first char here
|
|
// first char spaces must remain, in case the output area is just one char width
|
|
if (c != '-' && !fIsFirstLineChar) --szLastBreakPos; // because c<256, the character length can be safely assumed to be 1 here
|
|
iXBreak = iX;
|
|
}
|
|
// always mark emergency break after char that fitted the line
|
|
szLastEmergenyBreakPos = szPos;
|
|
iXEmergencyBreak = iX;
|
|
szLastEmergencyBreakOut = szOut;
|
|
// line OK; continue filling it
|
|
fIsFirstLineChar = false;
|
|
continue;
|
|
}
|
|
// line must be broken now
|
|
// if there was no linebreak, do it at emergency pos
|
|
if (szLastBreakPos == szMsg)
|
|
{
|
|
szLastBreakPos = szLastEmergenyBreakPos;
|
|
szLastBreakOut = szLastEmergencyBreakOut;
|
|
iXBreak = iXEmergencyBreak;
|
|
}
|
|
// insert linebreak at linebreak pos
|
|
// was it a space? Then just overwrite space with a linebreak
|
|
if (uint8_t(*szLastBreakPos)<128 && isspace((unsigned char)*szLastBreakPos))
|
|
*(szLastBreakOut-1) = '\n';
|
|
else
|
|
{
|
|
// otherwise, insert line break
|
|
if (szOut && !--iMaxOutLen)
|
|
// buffer is full
|
|
break;
|
|
if (szOut)
|
|
{
|
|
char *szOut2 = szOut;
|
|
while (--szOut2 >= szLastBreakOut)
|
|
szOut2[1] = *szOut2;
|
|
szOut2[1] = '\n';
|
|
}
|
|
}
|
|
// calc next line usage
|
|
iX -= iXBreak;
|
|
}
|
|
else
|
|
{
|
|
// a static linebreak: Everything's well; this just resets the line width
|
|
iX = 0;
|
|
}
|
|
// forced or manual line break: set new line beginning to char after line break
|
|
szLastBreakPos = szMsg = szPos;
|
|
// manual line break or line width overflow: add char to next line
|
|
iHgt += iLineHgt;
|
|
fIsFirstLineChar = true;
|
|
}
|
|
// transfer final data to buffer - markup and terminator
|
|
if (szOut)
|
|
while (*szOut++ = *szLastPos++)
|
|
if (!--iMaxOutLen)
|
|
{
|
|
// buffer end: cut and terminate
|
|
*szOut = '\0';
|
|
break;
|
|
}
|
|
// return text height
|
|
return iHgt;
|
|
}
|
|
|
|
int CStdFont::BreakMessage(const char *szMsg, int iWdt, StdStrBuf *pOut, bool fCheckMarkup, float fZoom)
|
|
{
|
|
// safety
|
|
if (!szMsg || !pOut) return 0;
|
|
pOut->Clear();
|
|
uint32_t c;
|
|
const char *szPos=szMsg, // current parse position in the text
|
|
*szLastBreakPos = szMsg, // points to the char after at (whitespace) or after ('-') which text can be broken
|
|
*szLastEmergenyBreakPos, // same, but at last char in case no suitable linebreak could be found
|
|
*szLastPos; // last position until which buffer has been transferred to output
|
|
int iLastBreakOutLen, iLastEmergencyBreakOutLen; // size of output string at break positions
|
|
int iX=0, // current text width at parse pos
|
|
iXBreak=0, // text width as it was at last break pos
|
|
iXEmergencyBreak, // same, but at last char in case no suitable linebreak could be found
|
|
iHgt=iLineHgt; // total height of output text
|
|
bool fIsFirstLineChar = true;
|
|
// ignore any markup
|
|
CMarkup MarkupChecker(false);
|
|
// go through all text
|
|
while (*(szLastPos = szPos))
|
|
{
|
|
// ignore markup
|
|
if (fCheckMarkup) MarkupChecker.SkipTags(&szPos);
|
|
// get current char
|
|
c = GetNextCharacter(&szPos);
|
|
// done? (must check here, because markup-skip may have led to text end)
|
|
if (!c) break;
|
|
// manual break?
|
|
int iCharWdt = 0;
|
|
if (c != '\n' && (!fCheckMarkup || c != '|'))
|
|
{
|
|
// image?
|
|
int iImgLgt;
|
|
if (fCheckMarkup && c=='{' && szPos[0]=='{' && szPos[1]!='{' && (iImgLgt=SCharPos('}', szPos+1))>0 && szPos[iImgLgt+2]=='}')
|
|
{
|
|
char imgbuf[101];
|
|
SCopy(szPos+1, imgbuf, Min(iImgLgt, 100));
|
|
CFacet fct;
|
|
// image renderer initialized?
|
|
if (pCustomImages)
|
|
// try to get an image then
|
|
pCustomImages->GetFontImage(imgbuf, fct);
|
|
if (fct.Hgt)
|
|
{
|
|
// image found: adjust aspect by font height and calc appropriate width
|
|
iCharWdt = (fct.Wdt * iGfxLineHgt) / fct.Hgt;
|
|
}
|
|
else
|
|
{
|
|
// image renderer not hooked or ID not found, or surface not present: just ignore it
|
|
// printing it out wouldn't look better...
|
|
iCharWdt = 0;
|
|
}
|
|
// skip image tag
|
|
szPos+=iImgLgt+3;
|
|
}
|
|
else
|
|
{
|
|
// regular char
|
|
// look up character width in texture coordinates table
|
|
if (c >= ' ')
|
|
iCharWdt = int(fZoom * GetCharacterFacet(c).Wdt / iFontZoom) + iHSpace;
|
|
else
|
|
iCharWdt = 0; // OMFG ctrl char
|
|
}
|
|
// add chars to output
|
|
pOut->Append(szLastPos, szPos - szLastPos);
|
|
// add to line; always add one char at minimum
|
|
if ((iX+=iCharWdt) <= iWdt || fIsFirstLineChar)
|
|
{
|
|
// check whether linebreak possibility shall be marked here
|
|
// 2do: What about unicode-spaces?
|
|
if (c<256) if (isspace((unsigned char)c) || c == '-')
|
|
{
|
|
szLastBreakPos = szPos;
|
|
iLastBreakOutLen = pOut->getLength();
|
|
// space: Break directly at space if it isn't the first char here
|
|
// first char spaces must remain, in case the output area is just one char width
|
|
if (c != '-' && !fIsFirstLineChar) --szLastBreakPos; // because c<256, the character length can be safely assumed to be 1 here
|
|
iXBreak = iX;
|
|
}
|
|
// always mark emergency break after char that fitted the line
|
|
szLastEmergenyBreakPos = szPos;
|
|
iXEmergencyBreak = iX;
|
|
iLastEmergencyBreakOutLen = pOut->getLength();
|
|
// line OK; continue filling it
|
|
fIsFirstLineChar = false;
|
|
continue;
|
|
}
|
|
// line must be broken now
|
|
// check if a linebreak is possible directly here, because it's a space
|
|
// only check for space and not for other breakable characters (such as '-'), because the break would happen after those characters instead of at them
|
|
if (c<128 && isspace((unsigned char)c))
|
|
{
|
|
szLastBreakPos = szPos-1;
|
|
iLastBreakOutLen = pOut->getLength();
|
|
iXBreak = iX;
|
|
}
|
|
// if there was no linebreak, do it at emergency pos
|
|
else if (szLastBreakPos == szMsg)
|
|
{
|
|
szLastBreakPos = szLastEmergenyBreakPos;
|
|
iLastBreakOutLen = iLastEmergencyBreakOutLen;
|
|
iXBreak = iXEmergencyBreak;
|
|
}
|
|
// insert linebreak at linebreak pos
|
|
// was it a space? Then just overwrite space with a linebreak
|
|
if (uint8_t(*szLastBreakPos)<128 && isspace((unsigned char)*szLastBreakPos))
|
|
*pOut->getMPtr(iLastBreakOutLen-1) = '\n';
|
|
else
|
|
{
|
|
// otherwise, insert line break
|
|
pOut->InsertChar('\n', iLastBreakOutLen);
|
|
}
|
|
// calc next line usage
|
|
iX -= iXBreak;
|
|
}
|
|
else
|
|
{
|
|
// a static linebreak: Everything's well; this just resets the line width
|
|
iX = 0;
|
|
// add to output
|
|
pOut->Append(szLastPos, szPos - szLastPos);
|
|
}
|
|
// forced or manual line break: set new line beginning to char after line break
|
|
szLastBreakPos = szMsg = szPos;
|
|
// manual line break or line width overflow: add char to next line
|
|
iHgt += iLineHgt;
|
|
fIsFirstLineChar = true;
|
|
}
|
|
// transfer final data to buffer (any missing markup)
|
|
pOut->Append(szLastPos, szPos - szLastPos);
|
|
// return text height
|
|
return iHgt;
|
|
}
|
|
|
|
// get message break and pos after message break
|
|
// 2do: Function not ready for UTF-8, markup or inline images. Remove its usage using standardized BreakMessage
|
|
int CStdFont::GetMessageBreak(const char *szMsg, const char **ppNewPos, int iBreakWidth, float fZoom)
|
|
{
|
|
// safety
|
|
if (!szMsg || !*szMsg) { *ppNewPos = szMsg; return 0; }
|
|
const char *szPos = szMsg; unsigned char c;
|
|
int iWdt = 0; int iPos = 0;
|
|
// check all message until it's too wide
|
|
while (c = *szPos++)
|
|
{
|
|
++iPos;
|
|
// get char width
|
|
int iCharWdt = int(fZoom * fctAsciiTexCoords[c-' '].Wdt / iFontZoom) + iHSpace;
|
|
// add to overall line width
|
|
iWdt += iCharWdt;
|
|
// next char only if the line didn't overflow
|
|
if (iWdt > iBreakWidth) break;
|
|
}
|
|
// did it all fit?
|
|
if (!c)
|
|
{
|
|
// all OK then; use all the buffer
|
|
*ppNewPos = szPos-1;
|
|
return iPos;
|
|
}
|
|
// line must be broken - trace back until first break char
|
|
// szPos2 will be first char of next line
|
|
const char *szPos2 = szPos-1; int i=0;
|
|
while ((!i++ || *szPos2 != '-') && *szPos2 != ' ')
|
|
if (szPos2 == szMsg)
|
|
{
|
|
// do not go past beginning of line
|
|
// then better print out an unfitting break
|
|
szPos2 = szPos-1;
|
|
break;
|
|
}
|
|
else
|
|
--szPos2;
|
|
// but do print at least one char
|
|
if (szPos2 <= szMsg) szPos2 = szMsg+1;
|
|
// assign next line start pos - skip spaces
|
|
*ppNewPos = szPos2;
|
|
if (*szPos2 == ' ') ++*ppNewPos;
|
|
// return output string length
|
|
return szPos2 - szMsg;
|
|
}
|
|
|
|
|
|
|
|
|
|
/* Text drawing */
|
|
|
|
|
|
void CStdFont::DrawText(SURFACE sfcDest, float iX, float iY, DWORD dwColor, const char *szText, DWORD dwFlags, CMarkup &Markup, float fZoom)
|
|
{
|
|
CBltTransform bt, *pbt=NULL;
|
|
// set blit color
|
|
dwColor = InvertRGBAAlpha(dwColor);
|
|
DWORD dwOldModClr;
|
|
bool fWasModulated = lpDDraw->GetBlitModulation(dwOldModClr);
|
|
if (fWasModulated) ModulateClr(dwColor, dwOldModClr);
|
|
// get alpha fade percentage
|
|
DWORD dwAlphaMod = BoundBy<int>((((int)(dwColor>>0x18)-0x50)*0xff)/0xaf, 0, 255)<<0x18 | 0xffffff;
|
|
/* char TEXT[8192];
|
|
sprintf(TEXT, "%s(%x-%x-%x)", szText, dwAlphaMod>>0x18, dwColor>>0x15, (((int)(dwColor>>0x15)-0x50)*0xff)/0xaf); szText=TEXT;*/
|
|
// adjust text starting position (horizontal only)
|
|
if (dwFlags & STDFONT_CENTERED)
|
|
{
|
|
// centered
|
|
int32_t sx,sy;
|
|
GetTextExtent(szText, sx,sy, !(dwFlags & STDFONT_NOMARKUP));
|
|
sx = int(fZoom*sx); sy = int(fZoom*sy);
|
|
iX-=sx/2;
|
|
}
|
|
else if (dwFlags & STDFONT_RIGHTALGN)
|
|
{
|
|
// right-aligned
|
|
int32_t sx,sy;
|
|
GetTextExtent(szText, sx,sy, !(dwFlags & STDFONT_NOMARKUP));
|
|
sx = int(fZoom*sx); sy = int(fZoom*sy);
|
|
iX-=sx;
|
|
}
|
|
// apply texture zoom
|
|
fZoom /= iFontZoom;
|
|
// set start markup transformation
|
|
if (!Markup.Clean()) pbt=&bt;
|
|
// output text
|
|
uint32_t c;
|
|
CFacet fctFromBlt; // source facet
|
|
while (c = GetNextCharacter(&szText))
|
|
{
|
|
// ignore system characters
|
|
if (c < _T(' ')) continue;
|
|
// apply markup
|
|
if (c=='<' && (~dwFlags & STDFONT_NOMARKUP))
|
|
{
|
|
// get tag
|
|
if (Markup.Read(&--szText))
|
|
{
|
|
// mark transform to be done
|
|
// (done only if tag was found, so most normal blits don't init a trasnformation matrix)
|
|
pbt=&bt;
|
|
// skip the tag
|
|
continue;
|
|
}
|
|
// invalid tag: render it as text
|
|
++szText;
|
|
}
|
|
int w2, h2; // dst width/height
|
|
// custom image?
|
|
int iImgLgt;
|
|
if (c=='{' && szText[0]=='{' && szText[1]!='{' && (iImgLgt=SCharPos('}', szText+1))>0 && szText[iImgLgt+2]=='}' && !(dwFlags & STDFONT_NOMARKUP))
|
|
{
|
|
fctFromBlt.Default();
|
|
char imgbuf[101];
|
|
SCopy(szText+1, imgbuf, Min(iImgLgt, 100));
|
|
szText+=iImgLgt+3;
|
|
// image renderer initialized?
|
|
if (pCustomImages)
|
|
// try to get an image then
|
|
pCustomImages->GetFontImage(imgbuf, fctFromBlt);
|
|
if (fctFromBlt.Surface && fctFromBlt.Hgt)
|
|
{
|
|
// image found: adjust aspect by font height and calc appropriate width
|
|
w2 = (fctFromBlt.Wdt * iGfxLineHgt) / fctFromBlt.Hgt;
|
|
h2 = iGfxLineHgt;
|
|
}
|
|
else
|
|
{
|
|
// image renderer not hooked or ID not found, or surface not present: just ignore it
|
|
// printing it out wouldn't look better...
|
|
continue;
|
|
}
|
|
//normal: not modulated, unless done by transform or alpha fadeout
|
|
if ((dwColor>>0x18) <= 0x50)
|
|
lpDDraw->DeactivateBlitModulation();
|
|
else
|
|
lpDDraw->ActivateBlitModulation((dwColor&0xff000000) | 0xffffff);
|
|
}
|
|
else
|
|
{
|
|
// regular char
|
|
// get texture coordinates
|
|
fctFromBlt = GetCharacterFacet(c);
|
|
w2=int(fctFromBlt.Wdt*fZoom); h2=int(fctFromBlt.Hgt*fZoom);
|
|
lpDDraw->ActivateBlitModulation(dwColor);
|
|
}
|
|
// do color/markup
|
|
if (pbt)
|
|
{
|
|
// reset data to be transformed by markup
|
|
DWORD dwBlitClr = dwColor;
|
|
bt.Set(1,0,0,0,1,0,0,0,1);
|
|
// apply markup
|
|
Markup.Apply(bt, dwBlitClr);
|
|
if (dwBlitClr != dwColor) ModulateClrA(dwBlitClr, dwAlphaMod);
|
|
lpDDraw->ActivateBlitModulation(dwBlitClr);
|
|
// move transformation center to center of letter
|
|
float fOffX=(float) w2/2 + iX;
|
|
float fOffY=(float) h2/2 + iY;
|
|
bt.mat[2] += fOffX - fOffX*bt.mat[0] - fOffY*bt.mat[1];
|
|
bt.mat[5] += fOffY - fOffX*bt.mat[3] - fOffY*bt.mat[4];
|
|
}
|
|
// blit character or image
|
|
lpDDraw->Blit(fctFromBlt.Surface, float(fctFromBlt.X), float(fctFromBlt.Y), float(fctFromBlt.Wdt),float(fctFromBlt.Hgt),
|
|
sfcDest, iX, iY, float(w2), float(h2),
|
|
TRUE, pbt);
|
|
// advance pos and skip character indent
|
|
iX+=w2+iHSpace;
|
|
}
|
|
// reset blit modulation
|
|
if (fWasModulated)
|
|
lpDDraw->ActivateBlitModulation(dwOldModClr);
|
|
else
|
|
lpDDraw->DeactivateBlitModulation();
|
|
}
|
|
|
|
// The internal clonk charset is one of the windows charsets
|
|
// But to save the used one to the configuration, a string is used
|
|
// So we need to convert this string to the windows number for windows
|
|
// and RTF, and to the iconv name for iconv
|
|
//#define GB2312_CHARSET "CP936"
|
|
const char * GetCharsetCodeName(const char *strCharset)
|
|
{
|
|
// Match charset name to WinGDI codes
|
|
if (SEqualNoCase(strCharset, "SHIFTJIS")) return "CP932";
|
|
if (SEqualNoCase(strCharset, "HANGUL")) return "CP949";
|
|
if (SEqualNoCase(strCharset, "JOHAB")) return "CP1361";
|
|
if (SEqualNoCase(strCharset, "CHINESEBIG5")) return "CP950";
|
|
if (SEqualNoCase(strCharset, "GREEK")) return "CP1253";
|
|
if (SEqualNoCase(strCharset, "TURKISH")) return "CP1254";
|
|
if (SEqualNoCase(strCharset, "VIETNAMESE")) return "CP1258";
|
|
if (SEqualNoCase(strCharset, "HEBREW")) return "CP1255";
|
|
if (SEqualNoCase(strCharset, "ARABIC")) return "CP1256";
|
|
if (SEqualNoCase(strCharset, "BALTIC")) return "CP1257";
|
|
if (SEqualNoCase(strCharset, "RUSSIAN")) return "CP1251";
|
|
if (SEqualNoCase(strCharset, "THAI")) return "CP874";
|
|
if (SEqualNoCase(strCharset, "EASTEUROPE")) return "CP1250";
|
|
if (SEqualNoCase(strCharset, "UTF-8")) return "UTF-8";
|
|
// Default
|
|
return "CP1252";
|
|
}
|
|
BYTE GetCharsetCode(const char *strCharset)
|
|
{
|
|
// Match charset name to WinGDI codes
|
|
if (SEqualNoCase(strCharset, "SHIFTJIS")) return 128; // SHIFTJIS_CHARSET
|
|
if (SEqualNoCase(strCharset, "HANGUL")) return 129; // HANGUL_CHARSET
|
|
if (SEqualNoCase(strCharset, "JOHAB")) return 130; // JOHAB_CHARSET
|
|
if (SEqualNoCase(strCharset, "CHINESEBIG5")) return 136; // CHINESEBIG5_CHARSET
|
|
if (SEqualNoCase(strCharset, "GREEK")) return 161; // GREEK_CHARSET
|
|
if (SEqualNoCase(strCharset, "TURKISH")) return 162; // TURKISH_CHARSET
|
|
if (SEqualNoCase(strCharset, "VIETNAMESE")) return 163; // VIETNAMESE_CHARSET
|
|
if (SEqualNoCase(strCharset, "HEBREW")) return 177; // HEBREW_CHARSET
|
|
if (SEqualNoCase(strCharset, "ARABIC")) return 178; // ARABIC_CHARSET
|
|
if (SEqualNoCase(strCharset, "BALTIC")) return 186; // BALTIC_CHARSET
|
|
if (SEqualNoCase(strCharset, "RUSSIAN")) return 204; // RUSSIAN_CHARSET
|
|
if (SEqualNoCase(strCharset, "THAI")) return 222; // THAI_CHARSET
|
|
if (SEqualNoCase(strCharset, "EASTEUROPE")) return 238; // EASTEUROPE_CHARSET
|
|
if (SEqualNoCase(strCharset, "UTF-8")) return 0; // ANSI_CHARSET - UTF8 needs special handling
|
|
// Default
|
|
return 0; // ANSI_CHARSET
|
|
}
|
|
int32_t GetCharsetCodePage(const char *strCharset)
|
|
{
|
|
// Match charset name to WinGDI codes
|
|
if (SEqualNoCase(strCharset, "SHIFTJIS")) return 932;
|
|
if (SEqualNoCase(strCharset, "HANGUL")) return 949;
|
|
if (SEqualNoCase(strCharset, "JOHAB")) return 1361;
|
|
if (SEqualNoCase(strCharset, "CHINESEBIG5")) return 950;
|
|
if (SEqualNoCase(strCharset, "GREEK")) return 1253;
|
|
if (SEqualNoCase(strCharset, "TURKISH")) return 1254;
|
|
if (SEqualNoCase(strCharset, "VIETNAMESE")) return 1258;
|
|
if (SEqualNoCase(strCharset, "HEBREW")) return 1255;
|
|
if (SEqualNoCase(strCharset, "ARABIC")) return 1256;
|
|
if (SEqualNoCase(strCharset, "BALTIC")) return 1257;
|
|
if (SEqualNoCase(strCharset, "RUSSIAN")) return 1251;
|
|
if (SEqualNoCase(strCharset, "THAI")) return 874;
|
|
if (SEqualNoCase(strCharset, "EASTEUROPE")) return 1250;
|
|
if (SEqualNoCase(strCharset, "UTF-8")) return -1; // shouldn't be called
|
|
// Default
|
|
return 1252;
|
|
}
|