|
|
//========= Copyright � 1996-2005, Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//
//=============================================================================//
#include "cbase.h"
#include <stdio.h>
#include "hlfaceposer.h"
#include "CloseCaptionTool.h"
#include "choreowidgetdrawhelper.h"
#include <vgui/ILocalize.h>
using namespace vgui;
CloseCaptionTool *g_pCloseCaptionTool = 0;
#define STREAM_FONT "Tahoma"
#define STREAM_POINTSIZE 12
#define STREAM_LINEHEIGHT ( STREAM_POINTSIZE + 2 )
#define STREAM_WEIGHT FW_NORMAL
#define CAPTION_LINGER_TIME 1.5f
// FIXME: Yahn, what is this, it's coded up as a DELAY before when the closed caption is displayed. That seems odd.
#define CAPTION_PREDISPLAY_TIME 0.0f // 0.5f
// A work unit is a pre-processed chunk of CC text to display
// Any state changes (font/color/etc) cause a new work unit to be precomputed
// Moving onto a new line also causes a new Work Unit
// The width and height are stored so that layout can be quickly recomputed each frame
class CCloseCaptionWorkUnit { public: CCloseCaptionWorkUnit(); ~CCloseCaptionWorkUnit();
void SetWidth( int w ); int GetWidth() const;
void SetHeight( int h ); int GetHeight() const;
void SetPos( int x, int y ); void GetPos( int& x, int &y ) const;
void SetBold( bool bold ); bool GetBold() const;
void SetItalic( bool ital ); bool GetItalic() const;
void SetStream( const wchar_t *stream ); const wchar_t *GetStream() const;
void SetColor( const Color& clr ); const Color& GetColor() const;
int GetFontNumber() const { return CloseCaptionTool::GetFontNumber( m_bBold, m_bItalic ); } void Dump() { char buf[ 2048 ]; g_pLocalize->ConvertUnicodeToANSI( GetStream(), buf, sizeof( buf ) );
Msg( "x = %i, y = %i, w = %i h = %i text %s\n", m_nX, m_nY, m_nWidth, m_nHeight, buf ); }
private:
int m_nX; int m_nY; int m_nWidth; int m_nHeight;
bool m_bBold; bool m_bItalic; wchar_t *m_pszStream; Color m_Color; };
CCloseCaptionWorkUnit::CCloseCaptionWorkUnit() : m_nWidth(0), m_nHeight(0), m_bBold(false), m_bItalic(false), m_pszStream(0), m_Color( Color( 255, 255, 255 ) ) { }
CCloseCaptionWorkUnit::~CCloseCaptionWorkUnit() { delete[] m_pszStream; m_pszStream = NULL; }
void CCloseCaptionWorkUnit::SetWidth( int w ) { m_nWidth = w; }
int CCloseCaptionWorkUnit::GetWidth() const { return m_nWidth; }
void CCloseCaptionWorkUnit::SetHeight( int h ) { m_nHeight = h; }
int CCloseCaptionWorkUnit::GetHeight() const { return m_nHeight; }
void CCloseCaptionWorkUnit::SetPos( int x, int y ) { m_nX = x; m_nY = y; }
void CCloseCaptionWorkUnit::GetPos( int& x, int &y ) const { x = m_nX; y = m_nY; }
void CCloseCaptionWorkUnit::SetBold( bool bold ) { m_bBold = bold; }
bool CCloseCaptionWorkUnit::GetBold() const { return m_bBold; }
void CCloseCaptionWorkUnit::SetItalic( bool ital ) { m_bItalic = ital; }
bool CCloseCaptionWorkUnit::GetItalic() const { return m_bItalic; }
void CCloseCaptionWorkUnit::SetStream( const wchar_t *stream ) { delete[] m_pszStream; m_pszStream = NULL;
int len = wcslen( stream ); Assert( len < 4096 ); m_pszStream = new wchar_t[ len + 1 ]; wcsncpy( m_pszStream, stream, len ); m_pszStream[ len ] = L'\0'; }
const wchar_t *CCloseCaptionWorkUnit::GetStream() const { return m_pszStream ? m_pszStream : L""; }
void CCloseCaptionWorkUnit::SetColor( const Color& clr ) { m_Color = clr; }
const Color &CCloseCaptionWorkUnit::GetColor() const { return m_Color; }
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
class CCloseCaptionItem { public: CCloseCaptionItem( wchar_t *stream, float timetolive, float predisplay, bool valid ) : m_flTimeToLive( 0.0f ), m_bValid( false ), m_nTotalWidth( 0 ), m_nTotalHeight( 0 ), m_bSizeComputed( false ) { SetStream( stream ); SetTimeToLive( timetolive ); SetPreDisplayTime( CAPTION_PREDISPLAY_TIME + predisplay ); m_bValid = valid; m_bSizeComputed = false; }
CCloseCaptionItem( const CCloseCaptionItem& src ) { SetStream( src.m_szStream ); m_flTimeToLive = src.m_flTimeToLive; m_bValid = src.m_bValid; }
~CCloseCaptionItem( void ) { while ( m_Work.Count() > 0 ) { CCloseCaptionWorkUnit *unit = m_Work[ 0 ]; m_Work.Remove( 0 ); delete unit; } }
void SetStream( const wchar_t *stream) { wcsncpy( m_szStream, stream, sizeof( m_szStream ) / sizeof( wchar_t ) ); }
const wchar_t *GetStream() const { return m_szStream; }
void SetTimeToLive( float ttl ) { m_flTimeToLive = ttl; }
float GetTimeToLive( void ) const { return m_flTimeToLive; }
bool IsValid() const { return m_bValid; }
void SetHeight( int h ) { m_nTotalHeight = h; } int GetHeight() const { return m_nTotalHeight; } void SetWidth( int w ) { m_nTotalWidth = w; } int GetWidth() const { return m_nTotalWidth; }
void AddWork( CCloseCaptionWorkUnit *unit ) { m_Work.AddToTail( unit ); }
int GetNumWorkUnits() const { return m_Work.Count(); }
CCloseCaptionWorkUnit *GetWorkUnit( int index ) { Assert( index >= 0 && index < m_Work.Count() );
return m_Work[ index ]; }
void SetSizeComputed( bool computed ) { m_bSizeComputed = computed; }
bool GetSizeComputed() const { return m_bSizeComputed; }
void SetPreDisplayTime( float t ) { m_flPreDisplayTime = t; }
float GetPreDisplayTime() const { return m_flPreDisplayTime; } private: wchar_t m_szStream[ 256 ];
float m_flPreDisplayTime; float m_flTimeToLive; bool m_bValid; int m_nTotalWidth; int m_nTotalHeight;
bool m_bSizeComputed;
CUtlVector< CCloseCaptionWorkUnit * > m_Work; };
ICloseCaptionManager *closecaptionmanager = NULL;
CloseCaptionTool::CloseCaptionTool( mxWindow *parent ) : IFacePoserToolWindow( "CloseCaptionTool", "Close Caption" ), mxWindow( parent, 0, 0, 0, 0 ) { m_nLastItemCount = -1; closecaptionmanager = this;
m_hFonts[ CCFONT_NORMAL ] = CreateFont( -STREAM_POINTSIZE, 0, 0, 0, STREAM_WEIGHT, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, DEFAULT_PITCH, STREAM_FONT );
m_hFonts[ CCFONT_ITALIC ] = CreateFont( -STREAM_POINTSIZE, 0, 0, 0, STREAM_WEIGHT, TRUE, FALSE, FALSE, DEFAULT_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, DEFAULT_PITCH, STREAM_FONT ); m_hFonts[ CCFONT_BOLD ] = CreateFont( -STREAM_POINTSIZE, 0, 0, 0, 700, FALSE, FALSE, FALSE, DEFAULT_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, DEFAULT_PITCH, STREAM_FONT );
m_hFonts[ CCFONT_ITALICBOLD ] = CreateFont( -STREAM_POINTSIZE, 0, 0, 0, 700, TRUE, FALSE, FALSE, DEFAULT_CHARSET, OUT_TT_PRECIS, CLIP_DEFAULT_PRECIS, ANTIALIASED_QUALITY, DEFAULT_PITCH, STREAM_FONT ); }
CloseCaptionTool::~CloseCaptionTool( void ) { }
//-----------------------------------------------------------------------------
// Purpose:
// Input : dt -
//-----------------------------------------------------------------------------
void CloseCaptionTool::Think( float dt ) { int c = m_Items.Count(); int i;
// Pass one decay all timers
for ( i = 0 ; i < c ; ++i ) { CCloseCaptionItem *item = m_Items[ i ];
float predisplay = item->GetPreDisplayTime(); if ( predisplay > 0.0f ) { predisplay -= dt; predisplay = max( 0.0f, predisplay ); item->SetPreDisplayTime( predisplay ); } else { // remove time from actual playback
float ttl = item->GetTimeToLive(); ttl -= dt; ttl = max( 0.0f, ttl ); item->SetTimeToLive( ttl ); } }
// Pass two, remove from head until we get to first item with time remaining
bool foundfirstnondeletion = false; for ( i = 0 ; i < c ; ++i ) { CCloseCaptionItem *item = m_Items[ i ];
// Skip items not yet showing...
float predisplay = item->GetPreDisplayTime(); if ( predisplay > 0.0f ) { continue; }
float ttl = item->GetTimeToLive(); if ( ttl > 0.0f ) { foundfirstnondeletion = true; continue; }
// Skip the remainder of the items after we find the first/oldest active item
if ( foundfirstnondeletion ) { continue; }
delete item; m_Items.Remove( i ); --i; --c; }
if ( m_Items.Count() != m_nLastItemCount ) { redraw(); } m_nLastItemCount = m_Items.Count(); }
struct VisibleStreamItem { int height; CCloseCaptionItem *item; };
void CloseCaptionTool::redraw() { if ( !ToolCanDraw() ) return;
CChoreoWidgetDrawHelper drawHelper( this ); HandleToolRedraw( drawHelper );
RECT rcOutput; drawHelper.GetClientRect( rcOutput );
RECT rcText = rcOutput; drawHelper.DrawFilledRect( Color( 0, 0, 0 ), rcText ); drawHelper.DrawOutlinedRect( Color( 200, 245, 150 ), PS_SOLID, 2, rcText ); InflateRect( &rcText, -4, 0 );
int avail_width = rcText.right - rcText.left;
int totalheight = 0; int i; CUtlVector< VisibleStreamItem > visibleitems; int c = m_Items.Count(); for ( i = 0; i < c; i++ ) { CCloseCaptionItem *item = m_Items[ i ];
// Not ready for display yet.
if ( item->GetPreDisplayTime() > 0.0f ) { continue; }
if ( !item->GetSizeComputed() ) { ComputeStreamWork( drawHelper, avail_width, item ); } int itemheight = item->GetHeight();
totalheight += itemheight;
VisibleStreamItem si; si.height = itemheight; si.item = item;
visibleitems.AddToTail( si ); }
rcText.bottom -= 2; rcText.top = rcText.bottom - totalheight;
// Now draw them
c = visibleitems.Count(); for ( i = 0; i < c; i++ ) { VisibleStreamItem *si = &visibleitems[ i ];
int height = si->height; CCloseCaptionItem *item = si->item;
rcText.bottom = rcText.top + height;
DrawStream( drawHelper, rcText, item );
OffsetRect( &rcText, 0, height );
if ( rcText.top >= rcOutput.bottom ) break; } }
int CloseCaptionTool::handleEvent( mxEvent *event ) { //MDLCACHE_CRITICAL_SECTION_( g_pMDLCache );
int iret = 0;
if ( HandleToolEvent( event ) ) { return iret; }
return iret; }
bool CloseCaptionTool::PaintBackground() { redraw(); return false; }
void CloseCaptionTool::Reset( void ) { while ( m_Items.Count() > 0 ) { CCloseCaptionItem *i = m_Items[ 0 ]; delete i; m_Items.Remove( 0 ); } }
void CloseCaptionTool::Process( char const *tokenname, float duration, int languageid ) { bool valid = true; wchar_t stream[ 256 ]; if ( !LookupUnicodeText( languageid, tokenname, stream, sizeof( stream ) / sizeof( wchar_t ) ) ) { valid = false; g_pLocalize->ConvertANSIToUnicode( va( "--> Missing Caption[%s]", tokenname ), stream, sizeof( stream ) ); }
if ( !wcsncmp( stream, L"!!!", wcslen( L"!!!" ) ) ) { // It's in the text file, but hasn't been translated...
valid = false; }
// Nothing to do...
if ( wcslen( stream ) == 0 ) { return; }
float delay = 0.0f;
wchar_t phrase[ 1024 ]; wchar_t *out = phrase;
for ( const wchar_t *curpos = stream; curpos && *curpos != L'\0'; ++curpos ) { wchar_t cmd[ 256 ]; wchar_t args[ 256 ];
if ( SplitCommand( &curpos, cmd, args ) ) { if ( !wcscmp( cmd, L"delay" ) ) {
// End current phrase
*out = L'\0';
if ( wcslen( phrase ) > 0 ) { CCloseCaptionItem *item = new CCloseCaptionItem( phrase, duration + CAPTION_LINGER_TIME, delay, valid ); m_Items.AddToTail( item ); }
// Start new phrase
out = phrase;
// Delay must be positive
delay = max( 0.0f, (float)wcstod( args, NULL ) );
continue; } }
*out++ = *curpos; }
// End final phrase, if any
*out = L'\0'; if ( wcslen( phrase ) > 0 ) { CCloseCaptionItem *item = new CCloseCaptionItem( phrase, duration + CAPTION_LINGER_TIME, delay, valid ); m_Items.AddToTail( item ); } }
bool CloseCaptionTool::LookupUnicodeText( int languageId, char const *token, wchar_t *outbuf, size_t count ) { wchar_t *outstr = g_pLocalize->Find( token ); if ( !outstr ) { wcsncpy( outbuf, L"<can't find entry>", count ); return false; }
wcsncpy( outbuf, outstr, count );
return true; }
bool CloseCaptionTool::LookupStrippedUnicodeText( int languageId, char const *token, wchar_t *outbuf, size_t count ) { wchar_t *outstr = g_pLocalize->Find( token ); if ( !outstr ) { wcsncpy( outbuf, L"<can't find entry>", count ); return false; }
const wchar_t *curpos = outstr; wchar_t *out = outbuf; size_t outlen = 0; for ( ; curpos && *curpos != L'\0' && outlen < count; ++curpos ) { wchar_t cmd[ 256 ]; wchar_t args[ 256 ];
if ( SplitCommand( &curpos, cmd, args ) ) { continue; }
*out++ = *curpos; ++outlen; }
*out = L'\0';
return true; }
bool CloseCaptionTool::SplitCommand( wchar_t const **ppIn, wchar_t *cmd, wchar_t *args ) const { const wchar_t *in = *ppIn; const wchar_t *oldin = in;
if ( in[0] != L'<' ) { *ppIn += ( oldin - in ); return false; }
args[ 0 ] = 0; cmd[ 0 ]= 0; wchar_t *out = cmd; in++; while ( *in != L'\0' && *in != L':' && *in != L'>' && !V_isspace( *in ) ) { *out++ = *in++; } *out = L'\0';
if ( *in != L':' ) { *ppIn += ( in - oldin ); return true; }
in++; out = args; while ( *in != L'\0' && *in != L'>' ) { *out++ = *in++; } *out = L'\0';
//if ( *in == L'>' )
// in++;
*ppIn += ( in - oldin ); return true; }
struct WorkUnitParams { WorkUnitParams() { Q_memset( stream, 0, sizeof( stream ) ); out = stream; x = 0; y = 0; width = 0; bold = italic = false; clr = Color( 255, 255, 255 ); newline = false; }
~WorkUnitParams() { }
void Finalize() { *out = L'\0'; }
void Next() { // Restart output
Q_memset( stream, 0, sizeof( stream ) ); out = stream;
x += width;
width = 0; // Leave bold, italic and color alone!!!
if ( newline ) { newline = false; x = 0; y += STREAM_LINEHEIGHT; } }
int GetFontNumber() { return CloseCaptionTool::GetFontNumber( bold, italic ); } wchar_t stream[ 1024 ]; wchar_t *out;
int x; int y; int width; bool bold; bool italic; Color clr; bool newline; };
void CloseCaptionTool::AddWorkUnit( CCloseCaptionItem *item, WorkUnitParams& params ) { params.Finalize();
if ( wcslen( params.stream ) > 0 ) { CCloseCaptionWorkUnit *wu = new CCloseCaptionWorkUnit();
wu->SetStream( params.stream ); wu->SetColor( params.clr ); wu->SetBold( params.bold ); wu->SetItalic( params.italic ); wu->SetWidth( params.width ); wu->SetHeight( STREAM_LINEHEIGHT ); wu->SetPos( params.x, params.y );
int curheight = item->GetHeight(); int curwidth = item->GetWidth();
curheight = max( curheight, params.y + wu->GetHeight() ); curwidth = max( curwidth, params.x + params.width );
item->SetHeight( curheight ); item->SetWidth( curwidth );
// Add it
item->AddWork( wu );
params.Next(); } }
void CloseCaptionTool::ComputeStreamWork( CChoreoWidgetDrawHelper &helper, int available_width, CCloseCaptionItem *item ) { // Start with a clean param block
WorkUnitParams params;
const wchar_t *curpos = item->GetStream(); CUtlVector< Color > colorStack;
for ( ; curpos && *curpos != L'\0'; ++curpos ) { wchar_t cmd[ 256 ]; wchar_t args[ 256 ];
if ( SplitCommand( &curpos, cmd, args ) ) { if ( !wcscmp( cmd, L"cr" ) ) { params.newline = true; AddWorkUnit( item, params); } else if ( !wcscmp( cmd, L"clr" ) ) { AddWorkUnit( item, params );
if ( args[0] == 0 && colorStack.Count()>= 2) { colorStack.Remove( colorStack.Count() - 1 ); params.clr = colorStack[ colorStack.Count() - 1 ]; } else { int r, g, b; Color newcolor; if ( 3 == swscanf( args, L"%i,%i,%i", &r, &g, &b ) ) { newcolor = Color( r, g, b ); colorStack.AddToTail( newcolor ); params.clr = colorStack[ colorStack.Count() - 1 ]; } } } else if ( !wcscmp( cmd, L"playerclr" ) ) { AddWorkUnit( item, params );
if ( args[0] == 0 && colorStack.Count()>= 2) { colorStack.Remove( colorStack.Count() - 1 ); params.clr = colorStack[ colorStack.Count() - 1 ]; } else { // player and npc color selector
// e.g.,. 255,255,255:200,200,200
int pr, pg, pb, nr, ng, nb; Color newcolor; if ( 6 == swscanf( args, L"%i,%i,%i:%i,%i,%i", &pr, &pg, &pb, &nr, &ng, &nb ) ) { // FIXME: nothing in .vcds is ever from the player...
newcolor = /*item->IsFromPlayer()*/ false ? Color( pr, pg, pb ) : Color( nr, ng, nb ); colorStack.AddToTail( newcolor ); params.clr = colorStack[ colorStack.Count() - 1 ]; } } } else if ( !wcscmp( cmd, L"I" ) ) { AddWorkUnit( item, params ); params.italic = !params.italic; } else if ( !wcscmp( cmd, L"B" ) ) { AddWorkUnit( item, params ); params.bold = !params.bold; }
continue; }
HFONT useF = m_hFonts[ params.GetFontNumber() ]; int w = helper.CalcTextWidthW( useF, L"%c", *curpos );
if ( ( params.x + params.width ) + w > available_width ) { params.newline = true; AddWorkUnit( item, params ); } *params.out++ = *curpos; params.width += w; }
// Add the final unit.
params.newline = true; AddWorkUnit( item, params );
item->SetSizeComputed( true );
// DumpWork( item );
}
void CloseCaptionTool:: DumpWork( CCloseCaptionItem *item ) { int c = item->GetNumWorkUnits(); for ( int i = 0 ; i < c; ++i ) { CCloseCaptionWorkUnit *wu = item->GetWorkUnit( i ); wu->Dump(); } }
void CloseCaptionTool::DrawStream( CChoreoWidgetDrawHelper &helper, RECT &rcText, CCloseCaptionItem *item ) { int c = item->GetNumWorkUnits();
RECT rcOut; rcOut.left = rcText.left;
for ( int i = 0 ; i < c; ++i ) { int x = 0; int y = 0;
CCloseCaptionWorkUnit *wu = item->GetWorkUnit( i ); HFONT useF = m_hFonts[ wu->GetFontNumber() ];
wu->GetPos( x, y );
rcOut.left = rcText.left + x; rcOut.right = rcOut.left + wu->GetWidth(); rcOut.top = rcText.top + y; rcOut.bottom = rcOut.top + wu->GetHeight();
Color useColor = wu->GetColor();
if ( !item->IsValid() ) { useColor = Color( 255, 255, 255 ); rcOut.right += 2; helper.DrawFilledRect( Color( 100, 100, 40 ), rcOut ); }
helper.DrawColoredTextW( useF, useColor, rcOut, L"%s", wu->GetStream() );
} }
int CloseCaptionTool::GetFontNumber( bool bold, bool italic ) { if ( bold || italic ) { if( bold && italic ) { return CloseCaptionTool::CCFONT_ITALICBOLD; }
if ( bold ) { return CloseCaptionTool::CCFONT_BOLD; }
if ( italic ) { return CloseCaptionTool::CCFONT_ITALIC; } }
return CloseCaptionTool::CCFONT_NORMAL; }
|