1028 lines
25 KiB
C++
1028 lines
25 KiB
C++
//========= Copyright (c) 1996-2005, Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose: Implementation of vgui::TextImage control
|
|
//
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
|
|
#include <string.h>
|
|
#include <stdio.h>
|
|
#include <ctype.h>
|
|
#ifdef _PS3
|
|
#include <wctype.h>
|
|
#endif
|
|
#include <assert.h>
|
|
|
|
#include <vgui/IPanel.h>
|
|
#include <vgui/ISurface.h>
|
|
#include <vgui/IScheme.h>
|
|
#include <vgui/IInput.h>
|
|
#include <vgui/ILocalize.h>
|
|
#include <keyvalues.h>
|
|
|
|
#include <vgui_controls/TextImage.h>
|
|
#include <vgui_controls/Controls.h>
|
|
|
|
#include "tier0/dbg.h"
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include <tier0/memdbgon.h>
|
|
|
|
// enable this define if you want unlocalized strings logged to files unfound.txt and unlocalized.txt
|
|
// #define LOG_UNLOCALIZED_STRINGS
|
|
|
|
using namespace vgui;
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Constructor
|
|
//-----------------------------------------------------------------------------
|
|
TextImage::TextImage(const char *text) : Image()
|
|
{
|
|
_utext = NULL;
|
|
_textBufferLen = 0;
|
|
_font = INVALID_FONT;
|
|
_fallbackFont = INVALID_FONT;
|
|
_unlocalizedTextSymbol = INVALID_STRING_INDEX;
|
|
_drawWidth = 0;
|
|
_textBufferLen = 0;
|
|
_textLen = 0;
|
|
m_bNoShortcutSyntax = false;
|
|
m_bWrap = false;
|
|
m_bWrapCenter = false;
|
|
m_LineBreaks.RemoveAll();
|
|
m_LineXIndent.RemoveAll();
|
|
m_pwszEllipsesPosition = NULL;
|
|
m_bUseFallbackFont = false;
|
|
m_bRenderUsingFallbackFont = false;
|
|
m_bAllCaps = false;
|
|
m_bUseAsianWordWrapping = false;
|
|
m_bRecalculateTruncation = true;
|
|
|
|
SetText(text); // set the text.
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Constructor
|
|
//-----------------------------------------------------------------------------
|
|
TextImage::TextImage(const wchar_t *wszText) : Image()
|
|
{
|
|
_utext = NULL;
|
|
_textBufferLen = 0;
|
|
_font = INVALID_FONT;
|
|
_fallbackFont = INVALID_FONT;
|
|
_unlocalizedTextSymbol = INVALID_STRING_INDEX;
|
|
_drawWidth = 0;
|
|
_textBufferLen = 0;
|
|
_textLen = 0;
|
|
m_bNoShortcutSyntax = false;
|
|
m_bWrap = false;
|
|
m_bWrapCenter = false;
|
|
m_LineBreaks.RemoveAll();
|
|
m_LineXIndent.RemoveAll();
|
|
m_bUseFallbackFont = false;
|
|
m_bRenderUsingFallbackFont = false;
|
|
m_bAllCaps = false;
|
|
m_bUseAsianWordWrapping = false;
|
|
m_bRecalculateTruncation = true;
|
|
|
|
SetText(wszText); // set the text.
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Destructor
|
|
//-----------------------------------------------------------------------------
|
|
TextImage::~TextImage()
|
|
{
|
|
delete [] _utext;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: takes the string and looks it up in the localization file to convert it to unicode
|
|
//-----------------------------------------------------------------------------
|
|
void TextImage::SetText(const char *text)
|
|
{
|
|
if (!text)
|
|
{
|
|
text = "";
|
|
}
|
|
|
|
// check for localization
|
|
if (*text == '#')
|
|
{
|
|
// try lookup in localization tables
|
|
_unlocalizedTextSymbol = g_pVGuiLocalize->FindIndex(text + 1);
|
|
|
|
if (_unlocalizedTextSymbol != INVALID_STRING_INDEX)
|
|
{
|
|
wchar_t *unicode = g_pVGuiLocalize->GetValueByIndex(_unlocalizedTextSymbol);
|
|
SetText(unicode);
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
// could not find string
|
|
// debug code for logging unlocalized strings
|
|
#if defined(LOG_UNLOCALIZED_STRINGS)
|
|
if (*text)
|
|
{
|
|
// write out error to unfound.txt log file
|
|
static bool first = true;
|
|
FILE *f;
|
|
if (first)
|
|
{
|
|
first = false;
|
|
f = fopen("unfound.txt", "wt");
|
|
}
|
|
else
|
|
{
|
|
f = fopen("unfound.txt", "at");
|
|
}
|
|
|
|
if (f)
|
|
{
|
|
fprintf(f, "\"%s\"\n", text);
|
|
fclose(f);
|
|
}
|
|
}
|
|
#endif // LOG_UNLOCALIZED_STRINGS
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// debug code for logging unlocalized strings
|
|
#if defined(LOG_UNLOCALIZED_STRINGS)
|
|
if (text[0])
|
|
{
|
|
// setting a label to be ANSI text, write out error to unlocalized.txt log file
|
|
static bool first = true;
|
|
FILE *f;
|
|
if (first)
|
|
{
|
|
first = false;
|
|
f = fopen("unlocalized.txt", "wt");
|
|
}
|
|
else
|
|
{
|
|
f = fopen("unlocalized.txt", "at");
|
|
}
|
|
if (f)
|
|
{
|
|
fprintf(f, "\"%s\"\n", text);
|
|
fclose(f);
|
|
}
|
|
}
|
|
#endif // LOG_UNLOCALIZED_STRINGS
|
|
}
|
|
|
|
// convert the ansi string to unicode and use that
|
|
wchar_t unicode[1024];
|
|
g_pVGuiLocalize->ConvertANSIToUnicode(text, unicode, sizeof(unicode));
|
|
SetText(unicode);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets the width that the text can be.
|
|
//-----------------------------------------------------------------------------
|
|
void TextImage::SetDrawWidth(int width)
|
|
{
|
|
_drawWidth = width;
|
|
m_bRecalculateTruncation = true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Gets the width that the text can be.
|
|
//-----------------------------------------------------------------------------
|
|
void TextImage::GetDrawWidth(int &width)
|
|
{
|
|
width = _drawWidth;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: sets unicode text directly
|
|
//-----------------------------------------------------------------------------
|
|
void TextImage::SetText(const wchar_t *unicode, bool bClearUnlocalizedSymbol)
|
|
{
|
|
if ( bClearUnlocalizedSymbol )
|
|
{
|
|
// Clear out unlocalized text symbol so that changing dialog variables
|
|
// doesn't stomp over the custom unicode string we're being set to.
|
|
_unlocalizedTextSymbol = INVALID_STRING_INDEX;
|
|
}
|
|
|
|
if (!unicode)
|
|
{
|
|
unicode = L"";
|
|
}
|
|
|
|
// reallocate the buffer if necessary
|
|
_textLen = (short)wcslen(unicode);
|
|
if (_textLen >= _textBufferLen)
|
|
{
|
|
delete [] _utext;
|
|
_textBufferLen = (short)(_textLen + 1);
|
|
_utext = new wchar_t[_textBufferLen];
|
|
}
|
|
|
|
m_LineBreaks.RemoveAll();
|
|
m_LineXIndent.RemoveAll();
|
|
|
|
// store the text as unicode
|
|
wcscpy(_utext, unicode);
|
|
|
|
m_bRecalculateTruncation = true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Gets the text in the textImage
|
|
//-----------------------------------------------------------------------------
|
|
void TextImage::GetText(char *buffer, int bufferSize)
|
|
{
|
|
g_pVGuiLocalize->ConvertUnicodeToANSI(_utext, buffer, bufferSize);
|
|
|
|
if ( m_bAllCaps )
|
|
{
|
|
// Uppercase all the letters
|
|
for ( int i = Q_strlen( buffer ); i >= 0; --i )
|
|
{
|
|
buffer[ i ] = toupper( buffer[ i ] );
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Gets the text in the textImage
|
|
//-----------------------------------------------------------------------------
|
|
void TextImage::GetText(wchar_t *buffer, int bufLenInBytes)
|
|
{
|
|
V_wcsncpy(buffer, _utext, bufLenInBytes );
|
|
|
|
if ( m_bAllCaps )
|
|
{
|
|
// Uppercase all the letters
|
|
for ( int i = Q_wcslen( buffer ) - 1; i >= 0; --i )
|
|
{
|
|
buffer[ i ] = towupper( buffer[ i ] );
|
|
}
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: data accessor
|
|
//-----------------------------------------------------------------------------
|
|
void TextImage::GetUnlocalizedText(char *buffer, int bufferSize)
|
|
{
|
|
if (_unlocalizedTextSymbol != INVALID_STRING_INDEX)
|
|
{
|
|
const char *text = g_pVGuiLocalize->GetNameByIndex(_unlocalizedTextSymbol);
|
|
buffer[0] = '#';
|
|
Q_strncpy(buffer + 1, text, bufferSize - 1);
|
|
buffer[bufferSize-1] = 0;
|
|
}
|
|
else
|
|
{
|
|
GetText(buffer, bufferSize);
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: unlocalized text symbol
|
|
//-----------------------------------------------------------------------------
|
|
StringIndex_t TextImage::GetUnlocalizedTextSymbol()
|
|
{
|
|
return _unlocalizedTextSymbol;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Changes the current font
|
|
//-----------------------------------------------------------------------------
|
|
void TextImage::SetFont(HFont font)
|
|
{
|
|
_font = font;
|
|
m_bRecalculateTruncation = true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Gets the font of the text.
|
|
//-----------------------------------------------------------------------------
|
|
HFont TextImage::GetFont()
|
|
{
|
|
if ( m_bRenderUsingFallbackFont )
|
|
{
|
|
return _fallbackFont;
|
|
}
|
|
|
|
return _font;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Sets the size of the TextImage. This is directly tied to drawWidth
|
|
//-----------------------------------------------------------------------------
|
|
void TextImage::SetSize(int wide, int tall)
|
|
{
|
|
Image::SetSize(wide, tall);
|
|
_drawWidth = wide;
|
|
m_bRecalculateTruncation = true;
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Draw the Image on screen.
|
|
//-----------------------------------------------------------------------------
|
|
void TextImage::Paint()
|
|
{
|
|
int wide, tall;
|
|
GetSize(wide, tall);
|
|
|
|
if (!_utext || GetFont() == INVALID_FONT )
|
|
return;
|
|
|
|
if (m_bRecalculateTruncation)
|
|
{
|
|
if ( m_bWrap || m_bWrapCenter )
|
|
{
|
|
RecalculateNewLinePositions();
|
|
}
|
|
|
|
RecalculateEllipsesPosition();
|
|
}
|
|
|
|
DrawSetTextColor(GetColor());
|
|
HFont font = GetFont();
|
|
DrawSetTextFont(font);
|
|
|
|
int lineHeight = surface()->GetFontTall(font);
|
|
int x = 0, y = 0;
|
|
int iIndent = 0;
|
|
|
|
int px, py;
|
|
GetPos(px, py);
|
|
|
|
int currentLineBreak = 0;
|
|
|
|
if ( m_bWrapCenter && m_LineXIndent.Count() )
|
|
{
|
|
x = m_LineXIndent[0];
|
|
}
|
|
|
|
for (wchar_t *wsz = _utext; *wsz != 0; wsz++)
|
|
{
|
|
wchar_t ch = wsz[0];
|
|
|
|
if ( m_bAllCaps )
|
|
{
|
|
ch = towupper( ch );
|
|
}
|
|
|
|
// check for special characters
|
|
if (ch == '\r')
|
|
{
|
|
// ignore, just use \n for newlines
|
|
continue;
|
|
}
|
|
else if (ch == '\n')
|
|
{
|
|
// newline
|
|
iIndent++;
|
|
if ( m_bWrapCenter && iIndent < m_LineXIndent.Count() )
|
|
{
|
|
x = m_LineXIndent[iIndent];
|
|
}
|
|
else
|
|
{
|
|
x = 0;
|
|
}
|
|
y += lineHeight;
|
|
continue;
|
|
}
|
|
else if ((ch == '&') && !m_bNoShortcutSyntax)
|
|
{
|
|
// "&&" means draw a single ampersand, single one is a shortcut character
|
|
if (wsz[1] == '&')
|
|
{
|
|
// just move on and draw the second ampersand
|
|
wsz++;
|
|
}
|
|
else
|
|
{
|
|
// draw the underline, then continue to the next character without moving forward
|
|
#ifdef VGUI_DRAW_HOTKEYS_ENABLED
|
|
surface()->DrawSetTextPos(x + px, y + py);
|
|
surface()->DrawUnicodeChar('_');
|
|
#endif
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// see if we've hit the truncated portion of the string
|
|
if (wsz == m_pwszEllipsesPosition)
|
|
{
|
|
// string is truncated, draw ellipses
|
|
for (int i = 0; i < 3; i++)
|
|
{
|
|
surface()->DrawSetTextPos(x + px, y + py);
|
|
surface()->DrawUnicodeChar('.');
|
|
x += surface()->GetCharacterWidth(font, '.');
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (currentLineBreak != m_LineBreaks.Count())
|
|
{
|
|
if (wsz == m_LineBreaks[currentLineBreak])
|
|
{
|
|
// newline
|
|
iIndent++;
|
|
if ( m_bWrapCenter && iIndent < m_LineXIndent.Count() )
|
|
{
|
|
x = m_LineXIndent[iIndent];
|
|
}
|
|
else
|
|
{
|
|
x = 0;
|
|
}
|
|
|
|
y += lineHeight;
|
|
currentLineBreak++;
|
|
}
|
|
}
|
|
|
|
// Underlined text wants to draw the spaces anyway
|
|
#if defined( PLATFORM_POSIX ) && !defined( _PS3 )
|
|
float xPos = x;
|
|
|
|
wchar_t chBefore = 0;
|
|
wchar_t chAfter = 0;
|
|
if ( wsz > _utext )
|
|
chBefore = wsz[-1];
|
|
chAfter = wsz[1];
|
|
if ( m_bAllCaps )
|
|
{
|
|
chBefore = towupper( chBefore );
|
|
chAfter = towupper( chAfter );
|
|
}
|
|
float flWide = 0.0f, flabcA = 0.0f, flabcC = 0.0f;
|
|
surface()->GetKernedCharWidth( font, ch, chBefore, chAfter, flWide, flabcA, flabcC );
|
|
x += floor( flabcA + flWide + flabcC + 0.6f );
|
|
|
|
//printf( "moving pen to %f,%d\n", floor( xPos + flabcA + 0.6f ) + px, y + py );
|
|
surface()->DrawSetTextPos( floor( xPos + flabcA + 0.6f ) + px, y + py );
|
|
//printf( "drawing '%C' - (%f,%f,%f) - %f pixels\n", ch, flabcA, flWide, flabcC, floor( flabcA + flWide + 0.6f ) );
|
|
surface()->DrawUnicodeChar(ch);
|
|
#else
|
|
surface()->DrawSetTextPos( x + px, y + py);
|
|
surface()->DrawUnicodeChar(ch);
|
|
x+= surface()->GetCharacterWidth(font, ch);
|
|
#endif
|
|
}
|
|
|
|
// Useful debugging
|
|
/*
|
|
DrawSetColor(GetColor());
|
|
DrawOutlinedRect( 0,0, _drawWidth, tall );
|
|
*/
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Get the size of a text string in pixels
|
|
//-----------------------------------------------------------------------------
|
|
void TextImage::GetTextSize(int &wide, int &tall)
|
|
{
|
|
wide = 0;
|
|
tall = 0;
|
|
int maxWide = 0;
|
|
const wchar_t *text = _utext;
|
|
|
|
HFont font = _font;
|
|
if ( font == INVALID_FONT )
|
|
return;
|
|
|
|
if ( m_bWrap || m_bWrapCenter )
|
|
{
|
|
RecalculateNewLinePositions();
|
|
}
|
|
|
|
// For height, use the remapped font
|
|
int fontHeight = surface()->GetFontTall(GetFont());
|
|
tall = fontHeight;
|
|
|
|
int textLen = wcslen(text);
|
|
for (int i = 0; i < textLen; i++)
|
|
{
|
|
wchar_t ch = text[i];
|
|
|
|
if ( m_bAllCaps )
|
|
{
|
|
ch = towupper( ch );
|
|
}
|
|
|
|
// handle stupid special characters, these should be removed
|
|
if ( (ch == '&') && !m_bNoShortcutSyntax && (text[i + 1] != 0) )
|
|
{
|
|
continue;
|
|
}
|
|
|
|
int nCharWidth;
|
|
#if defined( PLATFORM_POSIX ) && !defined( _PS3 )
|
|
wchar_t chBefore = 0;
|
|
wchar_t chAfter = 0;
|
|
if ( i > 0 )
|
|
chBefore = text[i-1];
|
|
chAfter = text[i+1];
|
|
float flWide = 0.0f, flabcA, flabcC;
|
|
surface()->GetKernedCharWidth( font, ch, chBefore, chAfter, flWide, flabcA, flabcC );
|
|
// don't include a negative C measure here - over estimate slightly how long the string might be
|
|
flWide = flabcA + flWide;
|
|
if ( flabcC > 0.0 )
|
|
flWide += flabcC;
|
|
nCharWidth = floor( flWide + 0.6f );
|
|
#else
|
|
int a, b, c;
|
|
surface()->GetCharABCwide(font, ch, a, b, c);
|
|
nCharWidth = (a + b + c);
|
|
#endif
|
|
wide += nCharWidth;
|
|
|
|
if (ch == '\n')
|
|
{
|
|
tall += fontHeight;
|
|
if (wide > maxWide)
|
|
{
|
|
maxWide=wide;
|
|
}
|
|
wide=0; // new line, wide is reset...
|
|
}
|
|
|
|
if ( m_bWrap || m_bWrapCenter )
|
|
{
|
|
for (int j=0; j<m_LineBreaks.Count(); j++)
|
|
{
|
|
if ( &text[i] == m_LineBreaks[j] )
|
|
{
|
|
tall += fontHeight;
|
|
if ( wide > maxWide )
|
|
{
|
|
maxWide = wide;
|
|
}
|
|
|
|
wide = nCharWidth; // new line, wide is reset...
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
#ifdef PLATFORM_OSX
|
|
wide += 2;
|
|
if ( textLen < 3 )
|
|
wide += 3;
|
|
#endif
|
|
if (wide < maxWide)
|
|
{
|
|
// maxWide only gets set if a newline is in the label
|
|
wide = maxWide;
|
|
}
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Get the size of the text in the image
|
|
//-----------------------------------------------------------------------------
|
|
void TextImage::GetContentSize(int &wide, int &tall)
|
|
{
|
|
GetTextSize(wide, tall);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Resize the text image to the content size
|
|
//-----------------------------------------------------------------------------
|
|
void TextImage::ResizeImageToContent()
|
|
{
|
|
int wide, tall;
|
|
GetContentSize(wide, tall);
|
|
SetSize(wide, tall);
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Recalculates line breaks
|
|
//-----------------------------------------------------------------------------
|
|
void TextImage::RecalculateNewLinePositions()
|
|
{
|
|
HFont font = GetFont();
|
|
|
|
float charWidth;
|
|
float x = 0;
|
|
|
|
//int wordStartIndex = 0;
|
|
wchar_t *wordStartIndex = _utext;
|
|
float wordLength = 0;
|
|
bool hasWord = false;
|
|
bool justStartedNewLine = true;
|
|
bool wordStartedOnNewLine = true;
|
|
|
|
int startChar = 0;
|
|
|
|
// clear the line breaks list
|
|
m_LineBreaks.RemoveAll();
|
|
m_LineXIndent.RemoveAll();
|
|
|
|
// handle the case where this char is a new line, in that case
|
|
// we have already taken its break index into account above so skip it.
|
|
if (_utext[startChar] == '\r' || _utext[startChar] == '\n')
|
|
{
|
|
startChar++;
|
|
}
|
|
|
|
// If we're using Asian word wrap rules, we need to know if the current potential line break is due to a space
|
|
// or because the rules allow us to break between the previous character and the current character.
|
|
bool bStartAsianWordHere = m_bUseAsianWordWrapping;
|
|
|
|
// loop through all the characters
|
|
for (wchar_t *wsz = &_utext[startChar]; *wsz != 0; wsz++)
|
|
{
|
|
wchar_t ch = wsz[0];
|
|
|
|
if ( m_bAllCaps )
|
|
{
|
|
ch = towupper( ch );
|
|
}
|
|
|
|
// line break only on whitespace characters
|
|
if ( !isbreakablewspace(ch) )
|
|
{
|
|
if ( !hasWord || bStartAsianWordHere ) // Asian word-breaking doesn't require spaces between "words"
|
|
{
|
|
// Start a new word
|
|
wordStartIndex = wsz;
|
|
hasWord = true;
|
|
wordStartedOnNewLine = justStartedNewLine;
|
|
wordLength = 0;
|
|
}
|
|
//else append to the current word
|
|
}
|
|
else
|
|
{
|
|
// whitespace/punctuation character
|
|
// end the word
|
|
hasWord = false;
|
|
}
|
|
|
|
// Can we start a new word after the current character?
|
|
bStartAsianWordHere = m_bUseAsianWordWrapping && AsianWordWrap::CanBreakAfter( wsz );
|
|
|
|
// get the width
|
|
#if defined( PLATFORM_POSIX ) && !defined( _PS3 )
|
|
wchar_t chBefore = 0;
|
|
wchar_t chAfter = 0;
|
|
if ( wsz > _utext )
|
|
chBefore = wsz[-1];
|
|
chAfter = wsz[1];
|
|
float flWide = 0.0f, flabcA = 0.0f, flabcC = 0.0f;
|
|
surface()->GetKernedCharWidth( font, ch, chBefore, chAfter, flWide, flabcA, flabcC );
|
|
charWidth = floor(flWide + flabcA + flabcC + 0.6f);
|
|
#else
|
|
charWidth = surface()->GetCharacterWidth(font, ch);
|
|
#endif
|
|
if (!iswcntrl(ch))
|
|
{
|
|
justStartedNewLine = false;
|
|
}
|
|
|
|
// check to see if the word is past the end of the line [wordStartIndex, i)
|
|
if ((x + charWidth) > _drawWidth || ch == '\r' || ch == '\n')
|
|
{
|
|
justStartedNewLine = true;
|
|
hasWord = false;
|
|
|
|
if (ch == '\r' || ch == '\n')
|
|
{
|
|
// set the break at the current character
|
|
//don't do this, paint will manually wrap on newline chars
|
|
// m_LineBreaks.AddToTail(i);
|
|
}
|
|
else if (wordStartedOnNewLine)
|
|
{
|
|
// word is longer than a line, so set the break at the current cursor
|
|
m_LineBreaks.AddToTail(wsz);
|
|
}
|
|
else
|
|
{
|
|
// set it at the last word Start
|
|
m_LineBreaks.AddToTail(wordStartIndex);
|
|
|
|
// just back to reparse the next line of text
|
|
// ywb 8/1/07: Back off one extra char since the 'continue' will increment wsz for us by one in the for loop
|
|
wsz = wordStartIndex - 1;
|
|
}
|
|
|
|
// reset word length
|
|
wordLength = 0;
|
|
x = 0;
|
|
continue;
|
|
}
|
|
|
|
// add to the size
|
|
x += charWidth;
|
|
wordLength += charWidth;
|
|
}
|
|
|
|
RecalculateCenterWrapIndents();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Calculates where the text should be truncated
|
|
//-----------------------------------------------------------------------------
|
|
void TextImage::RecalculateEllipsesPosition()
|
|
{
|
|
m_bRecalculateTruncation = false;
|
|
m_pwszEllipsesPosition = NULL;
|
|
|
|
//don't insert ellipses on wrapped strings
|
|
if ( m_bWrap || m_bWrapCenter )
|
|
return;
|
|
|
|
// don't truncate strings with newlines
|
|
if (wcschr(_utext, '\n') != NULL)
|
|
return;
|
|
|
|
if ( _drawWidth == 0 )
|
|
{
|
|
int h;
|
|
GetSize( _drawWidth, h );
|
|
}
|
|
|
|
for ( int check = 0; check < (m_bUseFallbackFont ? 2 : 1); ++check )
|
|
{
|
|
HFont font = GetFont();
|
|
if ( check == 1 && _fallbackFont != INVALID_FONT )
|
|
{
|
|
m_pwszEllipsesPosition = NULL;
|
|
font = _fallbackFont;
|
|
m_bRenderUsingFallbackFont = true;
|
|
}
|
|
|
|
int ellipsesWidth = 3 * surface()->GetCharacterWidth(font, '.');
|
|
int x = 0;
|
|
|
|
for (wchar_t *wsz = _utext; *wsz != 0; wsz++)
|
|
{
|
|
wchar_t ch = wsz[0];
|
|
|
|
if ( m_bAllCaps )
|
|
{
|
|
ch = towupper( ch );
|
|
}
|
|
|
|
// check for special characters
|
|
if (ch == '\r')
|
|
{
|
|
// ignore, just use \n for newlines
|
|
continue;
|
|
}
|
|
else if ((ch == '&') && !m_bNoShortcutSyntax)
|
|
{
|
|
// "&&" means draw a single ampersand, single one is a shortcut character
|
|
if (wsz[1] == '&')
|
|
{
|
|
// just move on and draw the second ampersand
|
|
wsz++;
|
|
}
|
|
else
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
|
|
#if defined( PLATFORM_POSIX ) && !defined( _PS3 )
|
|
wchar_t chBefore = 0;
|
|
wchar_t chAfter = 0;
|
|
if ( wsz > _utext )
|
|
chBefore = wsz[-1];
|
|
chAfter = wsz[1];
|
|
float flWide = 0.0f, flabcA = 0.0f, flabcC = 0.0f;
|
|
surface()->GetKernedCharWidth( font, ch, chBefore, chAfter, flWide, flabcA, flabcC );
|
|
float len = floor(flWide + flabcA + flabcC + 0.6f);
|
|
#else
|
|
float len = surface()->GetCharacterWidth(font, ch);
|
|
#endif
|
|
|
|
// don't truncate the first character
|
|
if (wsz == _utext)
|
|
{
|
|
x += len;
|
|
continue;
|
|
}
|
|
|
|
if (x + len + ellipsesWidth > _drawWidth)
|
|
{
|
|
// potential have an ellipses, see if the remaining characters will fit
|
|
int remainingLength = len;
|
|
for (const wchar_t *rwsz = wsz + 1; *rwsz != 0; rwsz++)
|
|
{
|
|
#if defined( PLATFORM_POSIX ) && !defined( _PS3 )
|
|
wchar_t chBefore = 0;
|
|
wchar_t chAfter = 0;
|
|
if ( rwsz > _utext )
|
|
chBefore = *(rwsz-1);
|
|
chAfter = *(rwsz+1);
|
|
float flWide = 0.0f, flabcA = 0.0f, flabcC = 0.0f;
|
|
surface()->GetKernedCharWidth( font, *rwsz, chBefore, chAfter, flWide, flabcA, flabcC );
|
|
remainingLength += floor(flWide + flabcA + flabcC + 0.6f);
|
|
#else
|
|
remainingLength += surface()->GetCharacterWidth(font, *rwsz);
|
|
#endif
|
|
}
|
|
|
|
if (x + remainingLength > _drawWidth)
|
|
{
|
|
// looks like we've got an ellipses situation
|
|
m_pwszEllipsesPosition = wsz;
|
|
break;
|
|
}
|
|
}
|
|
|
|
x += len;
|
|
}
|
|
|
|
// Didn't need ellipses...
|
|
if ( !m_pwszEllipsesPosition )
|
|
break;
|
|
}
|
|
}
|
|
|
|
void TextImage::SetUseAsianWordWrapping()
|
|
{
|
|
static bool bCheckForAsianLanguage = false;
|
|
static bool bIsAsianLanguage = false;
|
|
|
|
if ( !bCheckForAsianLanguage )
|
|
{
|
|
bCheckForAsianLanguage = true;
|
|
const char *pLanguage = scheme()->GetLanguage();
|
|
if ( pLanguage )
|
|
{
|
|
if ( !V_stricmp( pLanguage, "japanese" ) ||
|
|
!V_stricmp( pLanguage, "schinese" ) ||
|
|
!V_stricmp( pLanguage, "tchinese" ) )
|
|
{
|
|
bIsAsianLanguage = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
m_bUseAsianWordWrapping = bIsAsianLanguage;
|
|
}
|
|
|
|
void TextImage::SetWrap( bool bWrap )
|
|
{
|
|
m_bWrap = bWrap;
|
|
if ( m_bWrap )
|
|
{
|
|
SetUseAsianWordWrapping();
|
|
}
|
|
}
|
|
|
|
void TextImage::SetCenterWrap( bool bWrap )
|
|
{
|
|
m_bWrapCenter = bWrap;
|
|
if ( m_bWrapCenter )
|
|
{
|
|
SetUseAsianWordWrapping();
|
|
}
|
|
}
|
|
|
|
void TextImage::SetNoShortcutSyntax( bool bNoShortcutSyntax )
|
|
{
|
|
m_bNoShortcutSyntax = bNoShortcutSyntax;
|
|
}
|
|
|
|
|
|
void TextImage::SetUseFallbackFont( bool bState, HFont hFallback )
|
|
{
|
|
m_bUseFallbackFont = bState;
|
|
_fallbackFont = hFallback;
|
|
}
|
|
|
|
void TextImage::SetAllCaps( bool bAllCaps )
|
|
{
|
|
m_bAllCaps = bAllCaps;
|
|
}
|
|
|
|
void TextImage::ResizeImageToContentMaxWidth( int nMaxWidth )
|
|
{
|
|
_drawWidth = nMaxWidth;
|
|
// Since we might have to use the "fallback" font, go ahead and recalc the ellipses state first to see if that's the case
|
|
// NOTE: I think there may be a race condition lurking here, but this seems to work.
|
|
if ( m_bRecalculateTruncation )
|
|
{
|
|
if ( m_bWrap || m_bWrapCenter )
|
|
{
|
|
RecalculateNewLinePositions();
|
|
}
|
|
|
|
RecalculateEllipsesPosition();
|
|
}
|
|
|
|
ResizeImageToContent();
|
|
}
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: For center wrapping of multi-line text images, determines the indent each line needs to be centered
|
|
//-----------------------------------------------------------------------------
|
|
void TextImage::RecalculateCenterWrapIndents()
|
|
{
|
|
m_LineXIndent.RemoveAll();
|
|
|
|
if ( !m_bWrapCenter )
|
|
return;
|
|
|
|
if (!_utext || GetFont() == INVALID_FONT )
|
|
return;
|
|
|
|
HFont font = GetFont();
|
|
int px, py;
|
|
GetPos(px, py);
|
|
|
|
int currentLineBreak = 0;
|
|
float iCurLineW = 0;
|
|
|
|
for (wchar_t *wsz = _utext; *wsz != 0; wsz++)
|
|
{
|
|
wchar_t ch = wsz[0];
|
|
|
|
if ( m_bAllCaps )
|
|
{
|
|
ch = towupper( ch );
|
|
}
|
|
|
|
// check for special characters
|
|
if (ch == '\r')
|
|
{
|
|
// ignore, just use \n for newlines
|
|
continue;
|
|
}
|
|
else if (ch == '\n')
|
|
{
|
|
int iIdx = m_LineXIndent.AddToTail();
|
|
m_LineXIndent[iIdx] = (_drawWidth - iCurLineW) * 0.5;
|
|
|
|
iCurLineW = 0;
|
|
continue;
|
|
}
|
|
else if ((ch == '&') && !m_bNoShortcutSyntax)
|
|
{
|
|
// "&&" means draw a single ampersand, single one is a shortcut character
|
|
if (wsz[1] == '&')
|
|
{
|
|
// just move on and draw the second ampersand
|
|
wsz++;
|
|
}
|
|
else
|
|
{
|
|
// draw the underline, then continue to the next character without moving forward
|
|
continue;
|
|
}
|
|
}
|
|
|
|
// Don't need to check ellipses, they're not used when wrapping
|
|
|
|
if (currentLineBreak != m_LineBreaks.Count())
|
|
{
|
|
if (wsz == m_LineBreaks[currentLineBreak])
|
|
{
|
|
int iIdx = m_LineXIndent.AddToTail();
|
|
m_LineXIndent[iIdx] = (_drawWidth - iCurLineW) * 0.5;
|
|
|
|
iCurLineW = 0;
|
|
currentLineBreak++;
|
|
}
|
|
}
|
|
|
|
#if defined( PLATFORM_POSIX ) && !defined( _PS3 )
|
|
wchar_t chBefore = 0;
|
|
wchar_t chAfter = 0;
|
|
if ( wsz > _utext )
|
|
chBefore = wsz[-1];
|
|
chAfter = wsz[1];
|
|
float flWide = 0.0f, flabcA = 0.0f, flabcC = 0.0f;
|
|
surface()->GetKernedCharWidth( font, ch, chBefore, chAfter, flWide, flabcA, flabcC );
|
|
iCurLineW += floor(flWide + flabcA + flabcC + 0.6f);
|
|
#else
|
|
iCurLineW += surface()->GetCharacterWidth(font, ch);
|
|
#endif
|
|
}
|
|
|
|
// Add the final line
|
|
int iIdx = m_LineXIndent.AddToTail();
|
|
m_LineXIndent[iIdx] = (_drawWidth - iCurLineW) * 0.5;
|
|
}
|
|
|
|
void TextImage::SetBounds( int x, int y, int w, int h )
|
|
{
|
|
SetPos( x, y );
|
|
SetSize( w, h );
|
|
}
|
|
|
|
void TextImage::AddColorChange( Color col, int iTextStreamIndex )
|
|
{
|
|
label_colorchange_t tmpChange;
|
|
tmpChange.color = col;
|
|
tmpChange.textStreamIndex = iTextStreamIndex;
|
|
m_ColorChangeStream.Insert( tmpChange );
|
|
}
|