///////////////////////////////////////////////////////////////////////////////// // // FILE : TextFormat.cpp // PURPOSE : manages the formatting of the text // AUTHOR : Derek Payne // STARTED : 17/03/2009 // Copyright (C) 1999-2014 Rockstar Games. All Rights Reserved. // ///////////////////////////////////////////////////////////////////////////////// // rage #include "diag/output.h" // for DIAG_CONTEXT_MESSAGE #include "grcore/allocscope.h" #include "grcore/effect.h" #include "profile/element.h" #include "profile/group.h" #include "profile/page.h" #include "string/string.h" #include "string/unicode.h" #include "Text/TextFormat.h" #include "Text/TextConversion.h" #include "renderer/sprite2d.h" #include "renderer/DrawLists/DrawListMgr.h" #include "renderer/PostProcessFXHelper.h" #include "renderer/RenderTargets.h" #include "replaycoordinator/Storage/MontageText.h" #include "system/controlmgr.h" // mapper stuff #include "system/param.h" #include "shaders/ShaderLib.h" #include "camera/viewports/ViewportManager.h" #include "fwdrawlist/drawlistmgr.h" #include "fwsys/fileExts.h" #include "fwsys/timer.h" #include "streaming/streaming.h" #include "frontend/Scaleform/ScaleFormMgr.h" #include "bank/bank.h" #include "profile/timebars.h" TEXT_OPTIMISATIONS(); //OPTIMISATIONS_OFF() PF_PAGE(GTA_Text, "GTA Text Drawing"); PF_GROUP(GTA_TEXT); PF_LINK(GTA_Text, GTA_TEXT); PF_TIMER(Text_Flush, GTA_TEXT); PF_TIMER(Text_AddToBuffer, GTA_TEXT); PF_TIMER(Text_EndFrame, GTA_TEXT); PF_TIMER(Text_ParseToken, GTA_TEXT); PF_TIMER(Text_SetText, GTA_TEXT); PF_TIMER(Text_Measure, GTA_TEXT); #if KEYBOARD_MOUSE_SUPPORT #define KEYBOARD_KEY_FMT "t_" #define KEYBOARD_ICON_FMT "b_" #define UNMAPPED_ICON_TEXT KEYBOARD_ICON_FMT "995" // NOTE: This is not hook up in code yet but is hooked up in scaleform. The button id is 996 and the small icon is 997. #define KEYBOARD_LARGE_KEY_FMT "T_" #endif // KEYBOARD_MOUSE_SUPPORT #if __DEV //#define __DISPLAY_DEBUG_OUTPUT PARAM(nobuttons, "[code] disable buttons in text"); #endif #if __BANK char CTextFormat::ms_cDebugScriptedString[TEXT_KEY_SIZE]; #define TEXT_BUFFER_SIZE (32) // number of text draw commands we can queue at once #define GFXTEXT_CACHE_SIZE (96) // number of GfxDrawText instances to keep around #else #define TEXT_BUFFER_SIZE (32) // number of text draw commands we can queue in any frame #define GFXTEXT_CACHE_SIZE (32) // number of GfxDrawText instances to keep around - should be > the number of unique strings on screen #endif CompileTimeAssert(TEXT_BUFFER_SIZE <= GFXTEXT_CACHE_SIZE); #define NON_WIDESCREEN_SCALER (4.0f/3.0f) // for 4:3 scaling namespace CachedTextDrawing { struct sDrawCommand { Vector2 vPosition; GColor bgcolor; GRectF scissor; float fShadow; s16 iTextCacheIndex; u8 dropalpha; bool background, backgroundOutline, outline, outlineCut, adjustForNonWidescreen; u8 orientation; #if RSG_PC int StereoFlag; #endif }; struct sCache { struct Key { u32 hash; struct Blob { float width; int drawList; u32 stringHash; u32 fontNameHash; // see GFxDrawTextManager::TextParams GColor TextColor; GFxDrawText::Alignment HAlignment; GFxDrawText::VAlignment VAlignment; GFxDrawText::FontStyle FontStyle; Float FontSize; //GString FontName; bool Underline; bool Multiline; bool WordWrap; SInt Leading; SInt Indent; UInt LeftMargin; UInt RightMargin; }; Blob blob; Key() : hash(0) {} Key(const GFxDrawTextManager::TextParams& params, const char* string, float width, int drawList); bool operator==(const Key &rhs) const { return hash==rhs.hash && memcmp(&blob, &rhs.blob, sizeof(blob))==0; } bool operator!=(const Key &rhs) const { return !operator==(rhs); } }; struct Entry { Key key; u32 iLastUsed; // last frame this item was used GPtr pText; float fWidth; u16 iRefCount; #if __ASSERT u16 iRenderThreadIndex; #endif Entry() : iLastUsed(0) , fWidth(0.0f) , iRefCount(0) #if __ASSERT , iRenderThreadIndex(0xffff) #endif { } }; enum { INVALID_ENTRY = -1, }; int GetEntry(const Key& key, bool *isNewKey); void ReleaseEntry(int index); atRangeArray Cache; u32 Pending[NUMBER_OF_RENDER_THREADS]; sysCriticalSectionToken Mutex; }; atFixedArray DrawCommands[NUMBER_OF_RENDER_THREADS]; sCache GfxDrawTextCache; } /* static char ButtonFilenames[FONT_CHAR_TOKEN_ID_INPUT_ACCEPT-FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_NONE][40] = { "", "rstick_up", // FONT_CHAR_TOKEN_ID_CONTROLLER_UP, "rstick_down", // FONT_CHAR_TOKEN_ID_CONTROLLER_DOWN, "rstick_left", // FONT_CHAR_TOKEN_ID_CONTROLLER_LEFT, "rstick_right", // FONT_CHAR_TOKEN_ID_CONTROLLER_RIGHT, "dpad_up", // FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_UP, "dpad_down", // FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_DOWN, "dpad_left", // FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_LEFT, "dpad_right", // FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_RIGHT, "dpad_none", // FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_NONE, "dpad_all", // FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_ALL, "dpad_updown", // FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_UPDOWN, "dpad_leftright", // FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_LEFTRIGHT, "lstick_up", // FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_UP, "lstick_down", // FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_DOWN, "lstick_left", // FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_LEFT, "lstick_right", // FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_RIGHT, "lstick_none", // FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_NONE, "lstick_all", // FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_ALL, "lstick_updown", // FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_UPDOWN, "lstick_leftright", // FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_LEFTRIGHT, "lstick_rotate", // FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_ROTATE, "rstick_up", // FONT_CHAR_TOKEN_ID_CONTROLLER_RSTICK_UP, "rstick_down", // FONT_CHAR_TOKEN_ID_CONTROLLER_RSTICK_DOWN, "rstick_left", // FONT_CHAR_TOKEN_ID_CONTROLLER_RSTICK_LEFT, "rstick_right", // FONT_CHAR_TOKEN_ID_CONTROLLER_RSTICK_RIGHT, "rstick_none", // FONT_CHAR_TOKEN_ID_CONTROLLER_RSTICK_NONE, "rstick_all", // FONT_CHAR_TOKEN_ID_CONTROLLER_RSTICK_ALL, "rstick_updown", // FONT_CHAR_TOKEN_ID_CONTROLLER_RSTICK_UPDOWN, "rstick_leftright", // FONT_CHAR_TOKEN_ID_CONTROLLER_RSTICK_LEFTRIGHT, "rstick_rotate", // FONT_CHAR_TOKEN_ID_CONTROLLER_RSTICK_ROTATE, "p_cross", // FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_A, "p_circle", // FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_B, "p_square", // FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_X, "p_triangle", // FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_Y, "p_l1", // FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_LB, "p_l2", // FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_LT, "p_r1", // FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_RB, "p_r2", // FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_RT, "start_butt", // FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_START, "p_select", // FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_BACK, #if defined __PS3_BUTTONS "p_sixaxis_drive", // FONT_CHAR_TOKEN_ID_CONTROLLER_SIXAXIS_DRIVE, "p_sixaxis_pitch", // FONT_CHAR_TOKEN_ID_CONTROLLER_SIXAXIS_PITCH, "p_sixaxis_reload", // FONT_CHAR_TOKEN_ID_CONTROLLER_SIXAXIS_RELOAD, "p_sixaxis_roll" // FONT_CHAR_TOKEN_ID_CONTROLLER_SIXAXIS_ROLL, #endif }; */ #if __BANK static bool bOutputAllTextRenderToLog = false; #endif // __BANK bank_float CTextFormat::fTextIconScaler = 1.277f; // original value: 1.0 bank_float CTextFormat::fTextIconOffset = -0.32f; // original value: -0.40 const IconParams CTextFormat::DEFAULT_ICON_PARAMS; CachedTextDrawing::sCache::Key::Key(const GFxDrawTextManager::TextParams& params, const char* string, float width, int drawList) { // Hash up all the parameters that could cause text to get re-formatted blob.width = width; blob.drawList = drawList; blob.stringHash = atLiteralStringHash(string); blob.fontNameHash = atStringHash(params.FontName.ToCStr()); blob.TextColor = params.TextColor; blob.HAlignment = params.HAlignment; blob.VAlignment = params.VAlignment; blob.FontStyle = params.FontStyle; blob.FontSize = params.FontSize; blob.Underline = params.Underline; blob.Multiline = params.Multiline; blob.WordWrap = params.WordWrap; blob.Leading = params.Leading; blob.Indent = params.Indent; blob.LeftMargin = params.LeftMargin; blob.RightMargin = params.RightMargin; hash = atDataHash((const char*)&blob, sizeof(blob)); } int CachedTextDrawing::sCache::GetEntry(const Key& key, bool *isNewKey) { SYS_CS_SYNC(Mutex); Entry* elements = Cache.GetElements(); int numElements = Cache.GetMaxCount(); int oldest = INVALID_ENTRY; u32 oldestFrame = fwTimer::GetFrameCount(); *isNewKey = true; for(int i = 0; i < numElements; i++) { Entry& entry = elements[i]; // Found entry with matching hash? if (entry.key == key) { oldest = i; *isNewKey = false; break; } // Available LRU entry? if (entry.iRefCount == 0 && (s32)(oldestFrame - entry.iLastUsed) >= 0) { oldestFrame = entry.iLastUsed; oldest = i; } } if (oldest != INVALID_ENTRY) { const unsigned rti = g_RenderThreadIndex; Entry *e = elements+oldest; // If we are recycling a cache entry, then ensure we aren't going to // have too many pending entries from this render thread. If we have // too many, return INVALID_ENTRY to get caller to do a flush. if (!(e->iRefCount)++) { if (Pending[rti] >= GFXTEXT_CACHE_SIZE) { Assert(Pending[rti] == GFXTEXT_CACHE_SIZE); return INVALID_ENTRY; } ++(Pending[rti]); if (*isNewKey) { e->key = key; } #if __ASSERT Assert(e->iRenderThreadIndex == 0xffff); e->iRenderThreadIndex = (u16)rti; #endif } #if __ASSERT else { Assert(!*isNewKey); Assert(e->iRenderThreadIndex == rti); } #endif return oldest; } return INVALID_ENTRY; } void CachedTextDrawing::sCache::ReleaseEntry(int index) { Assert(index != INVALID_ENTRY); SYS_CS_SYNC(Mutex); sCache::Entry& e = Cache[index]; const unsigned rti = g_RenderThreadIndex; Assert(e.iRefCount); Assert(e.iRenderThreadIndex == rti); e.iLastUsed = fwTimer::GetFrameCount(); if (!--(e.iRefCount)) { #if __ASSERT e.iRenderThreadIndex = 0xffff; #endif Assert(Pending[rti]); --(Pending[rti]); } } void CTextFormat::Init() { // safecpy(ms_cDebugScriptedString, "WTD_BA_1", NELEM(ms_cDebugScriptedString)); } void CTextFormat::Shutdown() { using namespace CachedTextDrawing; for(int i = 0; i < GfxDrawTextCache.Cache.GetMaxCount(); i++) { GfxDrawTextCache.Cache[i].pText = NULL; } } __forceinline GColor ToGColor(Color32 c) { return GColor(c.GetRed(), c.GetGreen(), c.GetBlue(), c.GetAlpha()); } void CTextFormat::AddTextDrawCommand(const CTextLayout& FormatDetails, const GFxDrawTextManager::TextParams& params, const char* string, const Vector2& position, float width, bool bHtmlUsed) { using namespace CachedTextDrawing; // Pseudocode // Get a new draw command // Do we have too many? If so, flush them // Check the cache for a GFxDrawText object that contains the right text: // * Compute a hash based on the text layout and the string data // * check the cache for an existing entry with that hash // * If not found, grab the oldest _unreferenced_ cache item // * No oldest unreferenced cache item? // * flush and try again // const unsigned rti = g_RenderThreadIndex; if (DrawCommands[rti].IsFull()) { FlushFontBuffer(); } Assertf(pCurrExecutingList || g_RenderThreadIndex == 0, "If there is no currently executing drawlist then we should not be executing on a subrender thread"); int drawList = pCurrExecutingList ? pCurrExecutingList->m_type : -1; sCache::Key key(params, string, width, drawList); bool isNewKey; int cacheIndex = GfxDrawTextCache.GetEntry(key, &isNewKey); if (cacheIndex == sCache::INVALID_ENTRY) { FlushFontBuffer(); cacheIndex = GfxDrawTextCache.GetEntry(key, &isNewKey); // Fatal assert, as this will corrupt memory FatalAssertf(cacheIndex != sCache::INVALID_ENTRY, "Flushed the font buffer, but all cache entries were still referenced"); } // Cache entry hash will mismatch if there was a cache miss. sCache::Entry& entry = GfxDrawTextCache.Cache[cacheIndex]; if (isNewKey) { // Now fill out the new cache entry GRectF textBox(0.0f, 0.0f, width, 10000.0f); // start with a very large text box height to just let text flow down the screen bool needsTrueHeightExtent = FormatDetails.Background || FormatDetails.BackgroundOutline; GFxDrawTextManager* drawText = CScaleformMgr::GetMovieMgr()->GetDrawTextManager(); if (needsTrueHeightExtent) { GSizeF trueSize; if (bHtmlUsed) { trueSize = drawText->GetHtmlTextExtent(string, width, ¶ms); } else { trueSize = drawText->GetTextExtent(string, width, ¶ms); } trueSize.Expand(0.001f, 0.0f); textBox = GRectF(0.0f, 0.0f, trueSize); } if (bHtmlUsed) { entry.pText = *drawText->CreateHtmlText(string, textBox, ¶ms); } else { entry.pText = *drawText->CreateText(string, textBox, ¶ms); } entry.fWidth = width; entry.pText->SetAAMode(GFxDrawText::AA_Animation); } entry.iLastUsed = fwTimer::GetFrameCount(); sDrawCommand& cmd = DrawCommands[rti].Append(); cmd.iTextCacheIndex = (s16)cacheIndex; cmd.bgcolor = ToGColor(FormatDetails.BGColor); cmd.dropalpha = (u8)(FormatDetails.Color.GetAlpha()*0.75f); cmd.background = FormatDetails.Background; cmd.backgroundOutline = FormatDetails.BackgroundOutline; cmd.adjustForNonWidescreen = FormatDetails.bAdjustForNonWidescreen; cmd.orientation = FormatDetails.Orientation; #if RSG_PC cmd.StereoFlag = FormatDetails.StereoFlag; #endif if (FormatDetails.m_scissor.right != 0.0f && FormatDetails.m_scissor.bottom != 0.0f) // we have some width and height { cmd.scissor.SetRect(FormatDetails.m_scissor.left, FormatDetails.m_scissor.top, FormatDetails.m_scissor.right, FormatDetails.m_scissor.bottom); } else { cmd.scissor.Clear(); } if (FormatDetails.bDrop) { //#define DROP_SHADOW_SCALER (0.009f) // removed scaler as per bug 966734 from StuP cmd.outline = false; cmd.fShadow = 1.0f;//(DROP_SHADOW_SCALER * FormatDetails.Scale.y); } else if (FormatDetails.bOutline) { cmd.fShadow = 0.0f; cmd.outline = true; cmd.outlineCut = FormatDetails.bOutlineCutout; } else { cmd.fShadow = 0.0f; cmd.outline = false; } cmd.vPosition = position; } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CTextFormat::AddToTextBuffer // PURPOSE: adds text to the font buffer ///////////////////////////////////////////////////////////////////////////////////// #if SUPPORT_MULTI_MONITOR bool CTextFormat::AddToTextBuffer(float fXPos, float fYPos, const CTextLayout& FormatDetails, const char *pStringToRender, bool bEnableMultiHeadBoxWidthAdjustment) #else bool CTextFormat::AddToTextBuffer(float fXPos, float fYPos, const CTextLayout& FormatDetails, const char *pStringToRender) #endif // SUPPORT_MULTI_MONITOR { PF_FUNC(Text_AddToBuffer); if (!CText::AreScaleformFontsInitialised()) { return false; } char cTextString[MAX_CHARS_FOR_TEXT_STRING]; const grcViewport *pCurrentViewport = grcViewport::GetCurrent(); AssertMsg(pCurrentViewport, "CTextFormat - Invalid viewport"); if (!pCurrentViewport) { return false; // if no viewport return false } CTextLayout LocalFormatDetails = FormatDetails; Vector2 vViewportSize = Vector2((float)pCurrentViewport->GetWidth(), (float)pCurrentViewport->GetHeight()); if (FormatDetails.bAdjustForNonWidescreen) // adjust position, here - scale will be adjusted later in the render { fXPos /= NON_WIDESCREEN_SCALER; LocalFormatDetails.WrapStart /= NON_WIDESCREEN_SCALER; // fix for the wrap issue mentioned in LocalFormatDetails.WrapEnd /= NON_WIDESCREEN_SCALER; } // // setup the text params based on our current font layout: // Vector2 vFinalPos(0,0); GFxDrawTextManager::TextParams params; SetupFontParams(params, &LocalFormatDetails, fXPos, &vFinalPos); if (CTextConversion::GetByteCount(pStringToRender) >= MAX_CHARS_FOR_TEXT_STRING) { Assertf(0, "CTextFormat: Html string starting with '%s'... exceeds max character count of %d", pStringToRender, MAX_CHARS_FOR_TEXT_STRING); return false; } // // Get final formatted string: // bool bHtmlUsed = false; ParseToken(pStringToRender, cTextString, LocalFormatDetails.TextColours, &LocalFormatDetails.Color, params.FontSize, &bHtmlUsed, NELEM(cTextString), false); // go through, find and replace colour tokens with HTML tags if (cTextString[0] == 0) { /*#if __ASSERT char cTempDebugString[MAX_CHARS_FOR_TEXT_STRING]; sfDisplayf("Invalid String After Parse: %s", CTextConversion::charToAscii(pStringToRender, cTempDebugString, MAX_CHARS_FOR_TEXT_STRING)); AssertMsg(0, "CTextFormat: Text string contained nothing after parse - see log"); #endif // __ASSERT*/ return false; } // // get the final width and height used for this segment of text (only do this if we need to use the data) // GSizeF WidthAndHeight(10000.0f, 10000.0f); if (FormatDetails.RenderUpwards) { PF_START(Text_Measure); GFxDrawTextManager* drawText = CScaleformMgr::GetMovieMgr()->GetDrawTextManager(); if (bHtmlUsed) WidthAndHeight = drawText->GetHtmlTextExtent(cTextString, (vFinalPos.y-vFinalPos.x)*vViewportSize.x, ¶ms); else WidthAndHeight = drawText->GetTextExtent(cTextString, (vFinalPos.y-vFinalPos.x)*vViewportSize.x, ¶ms); PF_STOP(Text_Measure); fYPos -= (WidthAndHeight.Height / vViewportSize.y); // adjust so that we render at the bottom of the "window" } Vector2 position(vFinalPos.x * vViewportSize.x, fYPos * vViewportSize.y); float fBoxWidth = (vFinalPos.y - vFinalPos.x) * vViewportSize.x; #if SUPPORT_MULTI_MONITOR Vector2 vOriginalPosition(vFinalPos.x, vFinalPos.y); float fWidth = 1.0f; if (GRCDEVICE.GetMonitorConfig().isMultihead()) { const GridMonitor &mon = GRCDEVICE.GetMonitorConfig().getLandscapeMonitor(); const fwRect& area = mon.getArea(); vOriginalPosition -= Vector2(area.left, area.bottom); vOriginalPosition.Multiply(Vector2(1/area.GetWidth(), 1/area.GetHeight())); fWidth = (float)mon.getWidth(); if(bEnableMultiHeadBoxWidthAdjustment && (params.HAlignment != GFxDrawText::Align_Center)) fBoxWidth = (vOriginalPosition.y - vOriginalPosition.x) * fWidth; } #endif //SUPPORT_MULTI_MONITOR AddTextDrawCommand(FormatDetails, params, cTextString, position, fBoxWidth, bHtmlUsed); return true; // success } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CTextFormat::ParseToken // PURPOSE: parses any tokens that are in the text and sets up the correct html ///////////////////////////////////////////////////////////////////////////////////// void CTextFormat::ParseToken(const char *pStringIn, char *pStringOut, bool bFindColourTokens, const Color32 *pOriginalColour, float fButtonSize, bool *pUsingHtml, s32 iMaxStringLength, bool bStripColourTokens) { PF_FUNC(Text_ParseToken); #define MAX_CHARS_FOR_MINOR_HTML_CODE (10) #define MAX_CHARS_FOR_DETAILED_HTML_CODE (300) #define MAX_CHARS_FOR_KEY_NAME (16) if (!pStringOut) // ensure we have a valid output string { Assertf(0, "CTextFormat - Invalid output string passed"); return; } s32 iScaledButtonSize = (s32)rage::Floorf(fButtonSize * fTextIconScaler); const char *pNewCharacters = &pStringIn[0]; s32 iNewTextCount = 0; *pUsingHtml = false; pStringOut[0] = 0; enum eTOKEN_STATE { TOKEN_STATE_STANDARD_AREA, // standard text (not found a token yet) TOKEN_STATE_INSIDE_TOKEN_AREA, // inside a token, gathering all the token text TOKEN_STATE_COMPLETED, // fully completed, ready to go back to standard text again }; char cTokenName[MAX_TOKEN_LEN]; s32 iTokenNameCharCount = 0; bool bInsideBold = false; bool bInsideItalic = false; eTOKEN_STATE iTokenState = TOKEN_STATE_STANDARD_AREA; const char* prevChar = NULL; // loop through the text... while (*pNewCharacters != '\0' && *pNewCharacters != 0 && iNewTextCount < iMaxStringLength) { // CHECK TOKEN ESCAPING // If the current character is an escape character, and there is room to look ahead... if (*pNewCharacters == FONT_CHAR_TOKEN_ESCAPE && (iNewTextCount+1) < iMaxStringLength) { // Get the next character. If valid (not null), check if it is the delimiter. // If it is the delimiter, skip this escape character. The code below // will handle treating the delimiter as a normal character. const char* peek = (pNewCharacters+1); if (peek && *peek == FONT_CHAR_TOKEN_DELIMITER) { prevChar = pNewCharacters; pNewCharacters++; continue; } } // FOUND A TOKEN: if (*pNewCharacters == FONT_CHAR_TOKEN_DELIMITER && (prevChar == NULL || *prevChar != FONT_CHAR_TOKEN_ESCAPE)) // Check Escaping of Token { // START TOKEN - outside of token area, so enter it here and continue... if (iTokenState == TOKEN_STATE_STANDARD_AREA) { iTokenNameCharCount = 0; iTokenState = TOKEN_STATE_INSIDE_TOKEN_AREA; prevChar = pNewCharacters; pNewCharacters++; continue; } else // END TOKEN - inside token area, so we have at this point parsed the token so we process it here... if (iTokenState == TOKEN_STATE_INSIDE_TOKEN_AREA) { cTokenName[iTokenNameCharCount] = '\0'; iTokenNameCharCount = 0; // reset for next time bool bInsertColour = false; Color32 newColour; if (bFindColourTokens) { newColour = GetColourFromToken(cTokenName, &bInsertColour, pOriginalColour); if (bStripColourTokens) // stip the colour token but dont apply the hmtl { bInsertColour = false; } } IconList Icons; GetControllerIconsFromToken(cTokenName, Icons, pStringIn); char *pBlipToken = GetRadarBlipFromToken(cTokenName); s32 iInsertSpecific = GetSpecificToken(cTokenName); if (iInsertSpecific != FONT_CHAR_TOKEN_INVALID) { switch (iInsertSpecific) { case FONT_CHAR_TOKEN_ID_BOLD: { const char* htmlCode = bInsideBold ? "" : ""; bInsideBold = !bInsideBold; strcat( pStringOut, htmlCode ); iNewTextCount += fwTextUtil::GetByteCount(htmlCode); *pUsingHtml = true; break; } case FONT_CHAR_TOKEN_ID_ITALIC: { const char* htmlCode = bInsideItalic ? "" : ""; bInsideItalic = !bInsideItalic; strcat( pStringOut, htmlCode ); iNewTextCount += fwTextUtil::GetByteCount(htmlCode); *pUsingHtml = true; break; } case FONT_CHAR_TOKEN_ID_WANTED_STAR: { // // wanted star icon, so convert this to html tag and insert it into the text // char htmlCode[MAX_CHARS_FOR_DETAILED_HTML_CODE]; sprintf(htmlCode, "", iScaledButtonSize, iScaledButtonSize); strcat( pStringOut, htmlCode ); iNewTextCount += fwTextUtil::GetByteCount(htmlCode); *pUsingHtml = true; break; } case FONT_CHAR_TOKEN_ID_ROCKSTAR_LOGO: { // // rockstar logo icon, Using sigma which has been replaced on the character sheet. // const char* cWantedHtmlCode = "\xE2\x88\x91"; strcat( pStringOut, cWantedHtmlCode ); iNewTextCount += fwTextUtil::GetByteCount(cWantedHtmlCode); break; } case FONT_CHAR_TOKEN_ID_NEWLINE: { const char* htmlCode = "
"; strcat( pStringOut, htmlCode ); iNewTextCount += fwTextUtil::GetByteCount(htmlCode); *pUsingHtml = true; break; } case FONT_CHAR_TOKEN_ID_NON_RENDERABLE_TEXT: { break; } case FONT_CHAR_TOKEN_ID_DIALOGUE: { break; } default: { Assertf(0, "CTextFormat - Invalid specific code used: ~%c~", iInsertSpecific); break; } } } else if (bInsertColour) { // // found the colour token, so convert this to html tag and insert it into the text // char htmlCode[MAX_CHARS_FOR_DETAILED_HTML_CODE]; sprintf(htmlCode, "", newColour.GetRed(), newColour.GetGreen(), newColour.GetBlue()); strcat( pStringOut, htmlCode ); iNewTextCount += fwTextUtil::GetByteCount(htmlCode); *pUsingHtml = true; } else { if (pBlipToken) { // // found a radar blip, so convert this to html tag and insert it into the text // char htmlCode[MAX_CHARS_FOR_DETAILED_HTML_CODE]; for (s32 i = 0; i < (s32)strlen(pBlipToken); i++) pBlipToken[i] = (char)tolower(pBlipToken[i]); sprintf(htmlCode, "", pBlipToken, (s32)rage::Floorf(iScaledButtonSize * fTextIconOffset), iScaledButtonSize, iScaledButtonSize); strcat( pStringOut, htmlCode ); iNewTextCount += fwTextUtil::GetByteCount(htmlCode); *pUsingHtml = true; } else if(Icons.size() > 0) { char htmlCode[MAX_CHARS_FOR_DETAILED_HTML_CODE] = ""; IconParams params = DEFAULT_ICON_PARAMS; params.m_AllowXOSwap = false; GetIconListFormatString(Icons, htmlCode, MAX_CHARS_FOR_DETAILED_HTML_CODE, params); strcat( pStringOut, htmlCode ); iNewTextCount += fwTextUtil::GetByteCount(htmlCode); } else { // Check for "~~", allowing the escape character to be escaped if (*cTokenName == '\0') { char tildeCode[] = "~"; strcat( pStringOut, tildeCode ); iNewTextCount += fwTextUtil::GetByteCount(tildeCode); } else { #if __ASSERT // final check incase we didnt need the colour GetColourFromToken(cTokenName, &bInsertColour, pOriginalColour); // only assert if we didnt find a colour token (this step may of been skipped if not required by the render, so need to check here aswell) Assertf(bInsertColour, "CTextFormat - Invalid text token used: ~%s~. If all icons show up correctly the next frame ignore this assert.", cTokenName); #endif } } } // move onto the next character and drop out of the token areas: do { iTokenNameCharCount = 0; iTokenState = TOKEN_STATE_STANDARD_AREA; prevChar = pNewCharacters; pNewCharacters++; // if we had a newline this time, we need to skip past any space characters that come directly after it. // this allows line breaks in the next file to exist after newlines and also we want to avoid having invalid // spaces at the start of lines - this simply loops until the character isnt a space character anymore IF we // processed a newline this time. Fixes bugs like 116104 } while (iInsertSpecific == FONT_CHAR_TOKEN_ID_NEWLINE && *pNewCharacters == 32); } } else // No token found, so get the data // either from inside the token area: if (iTokenState == TOKEN_STATE_INSIDE_TOKEN_AREA) { Assert(iTokenNameCharCount < MAX_TOKEN_LEN - 1); if (iTokenNameCharCount < MAX_TOKEN_LEN - 1) { cTokenName[iTokenNameCharCount] = (char)*pNewCharacters; iTokenNameCharCount++; } prevChar = pNewCharacters; pNewCharacters++; } else // otherwise outside the token area: if (iTokenState == TOKEN_STATE_STANDARD_AREA) { // just copy over the existing chars pStringOut[iNewTextCount] = *pNewCharacters; iNewTextCount++; prevChar = pNewCharacters; pNewCharacters++; pStringOut[iNewTextCount] = 0; // must terminate each time as we may wish to append to this string } } if (CTextConversion::GetByteCount(pStringOut) < iMaxStringLength-MAX_CHARS_FOR_MINOR_HTML_CODE) { // // if bold or italic is left "open" then close these off in the text: // if (bInsideBold) { char boldCode[] = "
"; strcat( pStringOut, boldCode ); iNewTextCount += fwTextUtil::GetByteCount(boldCode); *pUsingHtml = true; } if (bInsideItalic) { char italicCode[] = ""; strcat( pStringOut, italicCode ); iNewTextCount += fwTextUtil::GetByteCount(italicCode); *pUsingHtml = true; } } pStringOut[iNewTextCount++] = 0; u32 iCharCount = fwTextUtil::GetByteCount(pStringOut); if (iCharCount >= iMaxStringLength) { Assertf(0, "Text string has more final characters (%d) after creating final string than max of %d", iCharCount, iMaxStringLength); pStringOut[iMaxStringLength-1] = 0; } } void CTextFormat::GetIconListFormatString(const IconList& icons, char* buffer, u32 bufferSize, const IconParams& iconParams) { buffer[0] = '\0'; const u32 TMP_BUFFER_SIZE = 15; char tmpBuffer[TMP_BUFFER_SIZE]; for(s32 i = 0; i < icons.size(); ++i) { tmpBuffer[0] = '\0'; const char* terminator; if(i == (icons.size() -1)) { terminator = ""; } else { terminator = KEY_FMT_SEPERATOR; } if(icons[i].GetIconText(tmpBuffer, TMP_BUFFER_SIZE, iconParams)) { safecat( buffer, tmpBuffer, bufferSize ); safecat( buffer, terminator, bufferSize ); } } } void CTextFormat::FillIconListFromTokenizedString(char* buffer, int* iIDs, int iIdSize, CTextFormat::IconList &icons) { //@TODO: This function is here for compatibility, but using a number to write to a string to then atoi the number back out is wasteful. // We'd need to split CTextFormat::Icon::GetIconText in two. if(iIDs != NULL) { const int COPY_BUFFER_SIZE = 64; char copyBuffer[COPY_BUFFER_SIZE]; char* tok = NULL; int iCount = 0; strcpy(copyBuffer, buffer); tok = strtok(copyBuffer, KEY_FMT_SEPERATOR); while(tok && iCount < iIdSize && iCount < icons.size()) { iIDs[iCount] = icons[iCount].m_IconId; if(tok[0] != 't' && tok[0] != 'T') { iIDs[iCount] = atoi(tok+2); // atoi past the 'b_' part of b_xxxx } tok = strtok(NULL, KEY_FMT_SEPERATOR); iCount++; } } } bool CTextFormat::GetInputButtons(const char* inputName, char* buffer, u32 bufferSize, int* iIDs, int iIdSize, const IconParams& iconParams) { return GetInputButtons(CControl::GetInputByName(inputName), buffer, bufferSize, iIDs, iIdSize, iconParams); } bool CTextFormat::GetInputButtons(InputType input, char* buffer, u32 bufferSize, int* iIDs, int iIdSize, const IconParams& iconParams) { ioSource source = CControlMgr::GetPlayerMappingControl().GetInputSource(input, iconParams.m_DeviceId, iconParams.m_MappingSlot, iconParams.m_AllowIconFallback); IconList icons; if(uiVerifyf(source.m_Device != IOMS_UNDEFINED && source.m_Parameter != ioSource::UNDEFINED_PARAMETER, "Invalid source for input '%s'! If all icons show up correctly the next frame ignore this.", CControl::GetInputName(input))) { CTextFormat::GetTokenIDFromPadButton(source.m_Device, source.m_Parameter, icons); if(uiVerifyf(icons.size() > 0, "No icon to display for '%s'!", CControl::GetInputName(input))) { CorrectButtonOrder(icons, iconParams); CTextFormat::GetIconListFormatString(icons, buffer, bufferSize, iconParams); CTextFormat::FillIconListFromTokenizedString(buffer, iIDs, iIdSize, icons); return true; } } return false; } bool CTextFormat::GetInputGroupButtons(const char* inputGroupName, char* buffer, u32 bufferSize, int* iIDs, int iIdSize, const IconParams& iconParams) { return GetInputGroupButtons(CControlMgr::GetPlayerMappingControl().GetInputGroupByName(inputGroupName), buffer, bufferSize, iIDs, iIdSize, iconParams); } bool CTextFormat::GetInputGroupButtons(InputGroup inputGroup, char* buffer, u32 bufferSize, int* iIDs, int iIdSize, const IconParams& iconParams) { CControl::InputGroupList igList; IconList icons; CControlMgr::GetPlayerMappingControl().GetInputsInGroup(inputGroup, igList); if(!igList.IsEmpty()) { CTextFormat::GroupSourceList slSources; for(u32 i = 0; i < igList.size(); ++i) { ioSource source = CControlMgr::GetPlayerMappingControl().GetInputSource(igList[i], iconParams.m_DeviceId, iconParams.m_MappingSlot, iconParams.m_AllowIconFallback); if( uiVerifyf(source.m_Device != IOMS_UNDEFINED && source.m_Parameter != ioSource::UNDEFINED_PARAMETER, "Invalid source for an input in input group '%s'!", CControl::GetInputGroupName(inputGroup)) ) { slSources.Push(source); } } CTextFormat::GetDeviceIconsFromInputGroup(slSources, icons); if(uiVerifyf(icons.size() > 0, "No icon to display for '%s'!", CControl::GetInputGroupName(inputGroup))) { CorrectButtonOrder(icons, iconParams); CTextFormat::GetIconListFormatString(icons, buffer, bufferSize, iconParams); CTextFormat::FillIconListFromTokenizedString(buffer, iIDs, iIdSize, icons); return true; } } else { uiAssertf(false,"INPUTGROUP (%s) does not contain any valid inputs.", CControl::GetInputGroupName(inputGroup)); } return false; } void CTextFormat::CorrectButtonOrder(IconList& icons, const IconParams& iconParams) { if(iconParams.m_CorrectButtonOrder) { Icon tmp; // We don't appear to have a reverse function so do it manually. for(s32 front = 0, back = icons.size() -1; front < back; ++front, --back) { tmp = icons[front]; icons[front] = icons[back]; icons[back] = tmp; } } } #define BLIP_STR_LEN 5 ///////////////////////////////////////////////////////////////////////////////////// // NAME: CTextFormat::GetRadarBlipFromToken // PURPOSE: retrieves the correct blip id based on the token number passed ///////////////////////////////////////////////////////////////////////////////////// char *CTextFormat::GetRadarBlipFromToken(char *pTokenText) { if (!strncmp("BLIP_", pTokenText, BLIP_STR_LEN)) { return pTokenText + BLIP_STR_LEN; } return NULL; } const char *CTextFormat::GetRadarBlipFromToken(const char *pTokenText) { if (!strncmp("BLIP_", pTokenText, BLIP_STR_LEN)) { return pTokenText + BLIP_STR_LEN; } return NULL; } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CTextFormat::GetControllerIconFromToken // PURPOSE: retrieves the correct controller pad icon based on the token ///////////////////////////////////////////////////////////////////////////////////// void CTextFormat::GetControllerIconsFromToken(char *pTokenText, IconList &Icons, const char* ASSERT_ONLY(pFullString)) { #if __ASSERT if (strncasecmp(pTokenText, "PAD_", 4) == 0) { if ( (strncasecmp(pTokenText, "PAD_RSTICK_ROTATE", 17) != 0) && (strncasecmp(pTokenText, "PAD_LSTICK_ROTATE", 17) != 0)) { Assertf(0, "Graeme - Support for ~PAD_ will soon be removed. Use an equivalent ~INPUT_ instead. Token is %s. Full string is %s", pTokenText, pFullString); } } #endif // __ASSERT if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_UP)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_UP); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_DOWN)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_DOWN); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_LEFT)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_LEFT); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_RIGHT)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_RIGHT); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_A)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_A); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_B)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_B); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_X)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_X); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_Y)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_Y); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_START)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_START); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_BACK)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_BACK); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_LB)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_LB); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_LT)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_LT); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_RB)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_RB); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_RT)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_RT); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_DPAD_UP)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_UP); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_DPAD_DOWN)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_DOWN); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_DPAD_LEFT)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_LEFT); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_DPAD_RIGHT)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_RIGHT); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_DPAD_NONE)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_NONE); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_DPAD_ALL)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_ALL); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_DPAD_UPDOWN)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_UPDOWN); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_DPAD_LEFTRIGHT)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_LEFTRIGHT); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_LSTICK_UP)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_UP); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_LSTICK_DOWN)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_DOWN); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_LSTICK_LEFT)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_LEFT); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_LSTICK_RIGHT)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_RIGHT); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_LSTICK_NONE)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_NONE); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_LSTICK_ALL)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_ALL); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_LSTICK_UPDOWN)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_UPDOWN); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_LSTICK_LEFTRIGHT)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_LEFTRIGHT); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_LSTICK_ROTATE)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_ROTATE); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_RSTICK_UP)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_RSTICK_UP); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_RSTICK_DOWN)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_RSTICK_DOWN); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_RSTICK_LEFT)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_RSTICK_LEFT); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_RSTICK_RIGHT)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_RSTICK_RIGHT); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_RSTICK_NONE)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_RSTICK_NONE); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_RSTICK_ALL)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_RSTICK_ALL); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_RSTICK_UPDOWN)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_RSTICK_UPDOWN); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_RSTICK_LEFTRIGHT)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_RSTICK_LEFTRIGHT); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_RSTICK_ROTATE)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_RSTICK_ROTATE); #if defined __PS3_BUTTONS else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_SIXAXIS_DRIVE)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_SIXAXIS_DRIVE); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_SIXAXIS_PITCH)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_SIXAXIS_PITCH); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_SIXAXIS_RELOAD)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_SIXAXIS_RELOAD); else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_CONTROLLER_SIXAXIS_ROLL)) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_SIXAXIS_ROLL); #endif // __PS3_BUTTONS else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_INPUT_ACCEPT)) { Assertf(false, "~%s~ in help text is not compatable with PC. It is possible that ~INPUT_FRONTEND_ACCEPT~ might be appropriate!", FONT_CHAR_TOKEN_INPUT_ACCEPT); SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_A); // here we can also check and return different values depenent on setup } else if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_INPUT_CANCEL)) { Assertf(false, "~%s~ in help text is not compatable with PC. It is possible that ~INPUT_FRONTEND_CANCEL~ might be appropriate!", FONT_CHAR_TOKEN_INPUT_CANCEL); SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_B); // here we can also check and return different values dependent on setup } else if (!::strnicmp(pTokenText, "INPUT_", 6)) { ioSource source = CControlMgr::GetPlayerMappingControl().GetInputSource(pTokenText); Assertf( source.m_Device != IOMS_UNDEFINED && source.m_Parameter != ioSource::UNDEFINED_PARAMETER, "No devices are mapped to %s! If all icons show up correctly the next frame ignore this.", pTokenText); if(source.m_Device != IOMS_UNDEFINED && source.m_Parameter != ioSource::UNDEFINED_PARAMETER) { GetTokenIDFromPadButton(source.m_Device, source.m_Parameter, Icons); } } else if(!::strnicmp(pTokenText, "INPUTGROUP_", 11)) { CControl::InputGroupList inputs; CControl::GetInputsInGroup(pTokenText, inputs); GroupSourceList sources; for(u32 i = 0; i < inputs.size(); ++i) { ioSource source = CControlMgr::GetPlayerMappingControl().GetInputSource(inputs[i]); if( Verifyf(source.m_Device != IOMS_UNDEFINED && source.m_Parameter != ioSource::UNDEFINED_PARAMETER, "An invalid sources for an input in input group '%s'!", pTokenText) ) { sources.Push(source); } } GetDeviceIconsFromInputGroup(sources, Icons); } } void CTextFormat::GetTokenIDFromPadButton(ioMapperSource PadSource, unsigned PadButton, IconList &Icons) { switch (PadSource) { case IOMS_PAD_AXIS : { switch (PadButton) { case IOM_AXIS_LX : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_LEFTRIGHT); break; case IOM_AXIS_LY : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_UPDOWN); break; case IOM_AXIS_RX : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_RSTICK_LEFTRIGHT); break; case IOM_AXIS_RY : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_RSTICK_UPDOWN); break; case IOM_AXIS_RY_UP : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_RSTICK_UP); break; case IOM_AXIS_RY_DOWN : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_RSTICK_DOWN); break; case IOM_AXIS_RX_LEFT : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_RSTICK_LEFT); break; case IOM_AXIS_RX_RIGHT : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_RSTICK_RIGHT); break; case IOM_AXIS_LY_UP : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_UP); break; case IOM_AXIS_LY_DOWN : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_DOWN); break; case IOM_AXIS_LX_LEFT : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_LEFT); break; case IOM_AXIS_LX_RIGHT : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_RIGHT); break; default : Assertf(0, "CFont::GetTokenIDFromPadButton - unhandled PadButton value within IOMS_PAD_AXIS"); break; } } break; case IOMS_PAD_ANALOGBUTTON : { switch (PadButton) { case ioPad::LRIGHT_INDEX : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_RIGHT); break; case ioPad::LLEFT_INDEX : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_LEFT); break; case ioPad::LUP_INDEX : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_UP); break; case ioPad::LDOWN_INDEX : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_DOWN); break; case ioPad::RUP_INDEX : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_Y); break; case ioPad::RRIGHT_INDEX : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_B); break; case ioPad::RDOWN_INDEX : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_A); break; case ioPad::RLEFT_INDEX : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_X); break; case ioPad::L1_INDEX : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_LB); break; case ioPad::R1_INDEX : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_RB); break; case ioPad::L2_INDEX : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_LT); break; case ioPad::R2_INDEX : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_RT); break; default : Assertf(0, "CFont::GetTokenIDFromPadButton - unhandled PadButton value within IOMS_PAD_ANALOGBUTTON"); break; } } break; case IOMS_PAD_DIGITALBUTTON : { switch (PadButton) { case ioPad::L2 : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_LT); break; case ioPad::R2 : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_RT); break; case ioPad::L1 : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_LB); break; case ioPad::R1 : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_RB); break; case ioPad::RUP : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_Y); break; case ioPad::RRIGHT : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_B); break; case ioPad::RDOWN : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_A); break; case ioPad::RLEFT : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_X); break; case ioPad::SELECT : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_BACK); break; case ioPad::L3 : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_NONE); // or FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_ALL break; case ioPad::R3 : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_RSTICK_NONE); // or FONT_CHAR_TOKEN_ID_CONTROLLER_RSTICK_ALL break; case ioPad::START : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_START); break; case ioPad::LUP : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_UP); break; case ioPad::LRIGHT : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_RIGHT); break; case ioPad::LDOWN : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_DOWN); break; case ioPad::LLEFT : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_LEFT); break; case ioPad::TOUCH : SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_BACK); // TODO: Change to the correct button icon once we have an icon for pressing the touch pad break; default : Assertf(0, "CFont::GetTokenIDFromPadButton - unhandled PadButton value (%d) within IOMS_PAD_DIGITALBUTTON (%d)", PadButton, PadSource); break; } } break; #if __WIN32PC case IOMS_KEYBOARD: { SafeAddIconToList(Icons, Icon(PadButton & 0xFF, Icon::KEYCODE_ID)); } break; case IOMS_MKB_AXIS: { // create negative button icon. Icon::Type type = Icon::KEYCODE_ID; ioMapperSource source = IOMS_KEYBOARD; if(ioMapper::IsMkbAxisNegativeAMouseButton(PadButton)) { type = Icon::MOUSE_ID; source = IOMS_MOUSE_BUTTON; } else if(ioMapper::IsMkbAxisNegativeAMouseWheel(PadButton)) { type = Icon::MOUSE_ID; source = IOMS_MOUSE_WHEEL; } ioMapperParameter param = ioMapper::ConvertParameterToMapperValue(source, ioMapper::GetMkbAxisNegative(PadButton)); SafeAddIconToList(Icons, Icon(param, type)); // create positive button icon. type = Icon::KEYCODE_ID; source = IOMS_KEYBOARD; if(ioMapper::IsMkbAxisPositiveAMouseButton(PadButton)) { type = Icon::MOUSE_ID; source = IOMS_MOUSE_BUTTON; } else if(ioMapper::IsMkbAxisPositiveAMouseWheel(PadButton)) { type = Icon::MOUSE_ID; source = IOMS_MOUSE_WHEEL; } param = ioMapper::ConvertParameterToMapperValue(source, ioMapper::GetMkbAxisPositive(PadButton)); SafeAddIconToList(Icons, Icon(param, type)); } break; case IOMS_MOUSE_BUTTON: case IOMS_MOUSE_ABSOLUTEAXIS: case IOMS_MOUSE_CENTEREDAXIS: case IOMS_MOUSE_RELATIVEAXIS: case IOMS_MOUSE_SCALEDAXIS: { ioMapperParameter param = ioMapper::ConvertParameterToMapperValue(PadSource, PadButton); if(param == IOM_IAXIS_X) { param = IOM_AXIS_X; } else if(param == IOM_IAXIS_Y) { param = IOM_AXIS_Y; } else if(ioMouse::IsLeftRightButtonSwapped()) { if(param == MOUSE_LEFT) { param = MOUSE_RIGHT; } else if(param == MOUSE_RIGHT) { param = MOUSE_LEFT; } } SafeAddIconToList(Icons, Icon(param, Icon::MOUSE_ID)); } break; case IOMS_MOUSE_WHEEL: { if(PadButton == IOM_WHEEL_UP || PadButton == IOM_WHEEL_DOWN) { SafeAddIconToList(Icons, Icon(PadButton, Icon::MOUSE_ID)); } else { SafeAddIconToList(Icons, Icon(Icon::MOUSE_WHL_UD, Icon::MOUSE_ID)); } } break; #endif // __WIN32PC #if USE_STEAM_CONTROLLER case IOMS_GAME_CONTROLLED: // NOTE: 0 is invalid so we do not use >= if(Verifyf(PadButton > 0 && PadButton < k_EControllerActionOrigin_Count, "Invalid steam icon id %u!", PadButton)) { // The steam icons start at 50, our valid ids start at 1 so the offset is 49. static const unsigned SteamIconOffset = 49; unsigned iconId = PadButton; // We only have 1 icon for all icons in this range. if(iconId > k_EControllerActionOrigin_Gyro_Move && iconId <= k_EControllerActionOrigin_Gyro_Roll) { iconId = k_EControllerActionOrigin_Gyro_Move; } // The icons for the A and B buttons had to be moved in scaleform as they overlapped with reserved values. Rather than redo all icons, // The A and B icon ids were moved to the end of the steam icons. else if(iconId == k_EControllerActionOrigin_A || iconId == k_EControllerActionOrigin_B) { // Move the icon id up to after the move icon. iconId += k_EControllerActionOrigin_Gyro_Move; } // The steam icon doesn't support icon collapsing so there could be duplicate icons. If so, do not add the icon again. const Icon steamIcon = SteamIconOffset + iconId; if(Icons.Find(steamIcon) == -1) { SafeAddIconToList(Icons, steamIcon); } } break; #endif // USE_STEAM_CONTROLLER default : { Assertf(0, "CFont::GetTokenIDFromPadButton - value of ioMapperSource not handled"); break; } } if(Icons.size() == 0) SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_NONE); } #if __WIN32PC bool IsXAxis(const ioSource& source) { return source.m_Parameter == IOM_AXIS_X || source.m_Parameter == IOM_IAXIS_X; } bool IsYAxis(const ioSource& source) { return source.m_Parameter == IOM_AXIS_Y || source.m_Parameter == IOM_IAXIS_Y; } #endif ///////////////////////////////////////////////////////////////////////////////////// // NAME: CTextFormat::GetDeviceIconsFromTwoAxis // PURPOSE: retrieves the correct icons for the two sources. ///////////////////////////////////////////////////////////////////////////////////// bool CTextFormat::GetDeviceIconsFromTwoAxis( const ioSource& UpDown, const ioSource& LeftRight, IconList& Icons ) { bool iconFound = false; bool sameDevice = UpDown.m_DeviceIndex == LeftRight.m_DeviceIndex && UpDown.m_Device == LeftRight.m_Device; // Pad-Left Stick if(sameDevice) { if((UpDown.m_Parameter == IOM_AXIS_LY && LeftRight.m_Parameter == IOM_AXIS_LX) || (UpDown.m_Parameter == IOM_AXIS_LX && LeftRight.m_Parameter == IOM_AXIS_LY)) { SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_ALL); iconFound = true; } // Pad-Right Stick else if((UpDown.m_Parameter == IOM_AXIS_RY && LeftRight.m_Parameter == IOM_AXIS_RX) || (UpDown.m_Parameter == IOM_AXIS_RX && LeftRight.m_Parameter == IOM_AXIS_RY)) { SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_RSTICK_ALL); iconFound = true; } #if __WIN32PC else if( (IsXAxis(UpDown) && IsYAxis(LeftRight)) || (IsXAxis(LeftRight) && IsYAxis(UpDown)) ) { SafeAddIconToList(Icons, Icon(Icon::MOUSE_MOV_ALL, Icon::MOUSE_ID)); iconFound = true; } else if((UpDown.m_Parameter == IOM_WHEEL_UP && LeftRight.m_Parameter == IOM_WHEEL_DOWN) || (UpDown.m_Parameter == IOM_WHEEL_DOWN && LeftRight.m_Parameter == IOM_WHEEL_UP)) { SafeAddIconToList(Icons, Icon(Icon::MOUSE_WHL_UD, Icon::MOUSE_ID)); iconFound = true; } // Really these should not come through as input groups but just in case we can collaps them. else if((UpDown.m_Parameter == IOM_AXIS_X_LEFT && LeftRight.m_Parameter == IOM_AXIS_X_RIGHT) || (UpDown.m_Parameter == IOM_AXIS_X_RIGHT && LeftRight.m_Parameter == IOM_AXIS_X_LEFT)) { SafeAddIconToList(Icons, Icon(Icon::MOUSE_MOV_LR, Icon::MOUSE_ID)); iconFound = true; } else if((UpDown.m_Parameter == IOM_AXIS_Y_UP && LeftRight.m_Parameter == IOM_AXIS_Y_DOWN) || (UpDown.m_Parameter == IOM_AXIS_Y_DOWN && LeftRight.m_Parameter == IOM_AXIS_Y_UP)) { SafeAddIconToList(Icons, Icon(Icon::MOUSE_MOV_UD, Icon::MOUSE_ID)); iconFound = true; } #endif // __WIN32PC else { u32 dpadFlags = GetDpadIconDirection(UpDown) | GetDpadIconDirection(LeftRight); if(dpadFlags == (DPAD_LEFT | DPAD_RIGHT)) { SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_LEFTRIGHT); iconFound = true; } else if(dpadFlags == (DPAD_UP | DPAD_DOWN)) { SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_UPDOWN); iconFound = true; } } } return iconFound; } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CTextFormat::GetDeviceIconsFromInputGroup // PURPOSE: retrieves the correct icons for the devices used for each input in the // group. ///////////////////////////////////////////////////////////////////////////////////// void CTextFormat::GetDeviceIconsFromInputGroup( const GroupSourceList& groupSources, IconList& Icons ) { // As we might alter the sources we will need a copy. GroupSourceList sources(groupSources); const IconList::size_type iconCount = Icons.size(); if(Verifyf(sources.size() > 0, "No inputs in the input group!")) { switch (sources.size()) { case IGT_SINGLE_OR_DOUBLE_AXIS: GetDeviceIconsFromTwoAxis(sources[0], sources[1], Icons); break; case IGT_DOUBLE_DPAD_AXIS: { u32 dpadFlags = DPAD_NONE; for(s32 i = 0; i < sources.size(); ++i) { dpadFlags |= GetDpadIconDirection(sources[i]); } // If all 4 directions of the dpad are found. if(dpadFlags == DPAD_ALL) { SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_ALL); } } break; case IGT_GENERIC_SINGLE_AXIS: case IGT_GENERIC_DOUBLE_AXIS: { // To simplify things we split the source types into groups of type. GroupSourceList axis; GroupSourceList buttons; #if RSG_PC GroupSourceList keyboardMouse; #endif // RSG_PC for(s32 i = 0; i < sources.size(); ++i) { #if RSG_PC // If the input is a keyboard axis or a mouse axis. if(sources[i].m_Device == IOMS_MKB_AXIS || (sources[i].m_Device >= IOMS_MOUSE_ABSOLUTEAXIS && sources[i].m_Device <= IOMS_MOUSE_SCALEDAXIS)) { keyboardMouse.Push(sources[i]); } // If the input is a joystick axis. else if(sources[i].m_Device >= IOMS_JOYSTICK_AXIS && sources[i].m_Device <= IOMS_JOYSTICK_AXIS_POSITIVE) { axis.Push(sources[i]); } // There are other keyboard mouse sources we do not care about! else if(sources[i].m_DeviceIndex != ioSource::IOMD_KEYBOARD_MOUSE) #endif // RSG_PC { if(sources[i].m_Device == IOMS_PAD_AXIS) { axis.Push(sources[i]); } else { buttons.Push(sources[i]); } } } #if RSG_PC // If there are any keyboard mouse controls then use them as a priority! if(keyboardMouse.size() > 0) { sources = keyboardMouse; } else #endif // RSG_PC { if(IsPossbleGenericAxis(buttons, axis)) { // use recursion to get the icons rather than copying the code. GetDeviceIconsFromInputGroup(buttons, Icons); GetDeviceIconsFromInputGroup(axis, Icons); ConvertToGenericAxisIcons(Icons); } } } break; default: break; } // If no icons were added. if(Icons.size() == iconCount) { for(s32 i = 0; i < sources.size(); ++i) { GetTokenIDFromPadButton(sources[i].m_Device, sources[i].m_Parameter, Icons); } } } } eHUD_COLOURS CTextFormat::ConvertToHudColour( eOverlayTextColours const memeColour ) { switch( memeColour ) { default: case eOverlayTextColours_White: return HUD_COLOUR_WHITE; case eOverlayTextColours_RedDark: return HUD_COLOUR_REDDARK; case eOverlayTextColours_Red: return HUD_COLOUR_RED; case eOverlayTextColours_RedLight: return HUD_COLOUR_REDLIGHT; case eOverlayTextColours_Pink: return HUD_COLOUR_PINK; case eOverlayTextColours_Purple: return HUD_COLOUR_PURPLE; case eOverlayTextColours_PurpleDark: return HUD_COLOUR_PURPLEDARK; case eOverlayTextColours_BlueDark: return HUD_COLOUR_BLUEDARK; case eOverlayTextColours_Blue: return HUD_COLOUR_BLUE; case eOverlayTextColours_BlueLight: return HUD_COLOUR_BLUELIGHT; case eOverlayTextColours_GreenLight: return HUD_COLOUR_GREENLIGHT; case eOverlayTextColours_Green: return HUD_COLOUR_GREEN; case eOverlayTextColours_GreenDark: return HUD_COLOUR_GREENDARK; case eOverlayTextColours_YellowLight: return HUD_COLOUR_YELLOWLIGHT; case eOverlayTextColours_Yellow: return HUD_COLOUR_YELLOW; case eOverlayTextColours_YellowDark: return HUD_COLOUR_YELLOWDARK; case eOverlayTextColours_OrangeLight: return HUD_COLOUR_ORANGELIGHT; case eOverlayTextColours_Orange: return HUD_COLOUR_ORANGE; case eOverlayTextColours_OrangeDark: return HUD_COLOUR_ORANGEDARK; case eOverlayTextColours_GreyLight: return HUD_COLOUR_GREYLIGHT; case eOverlayTextColours_Grey: return HUD_COLOUR_GREY; case eOverlayTextColours_GreyDark: return HUD_COLOUR_GREYDARK; case eOverlayTextColours_Black: return HUD_COLOUR_BLACK; } } eOverlayTextColours CTextFormat::ConvertToOverlayTextColour( eHUD_COLOURS const c_hudColor ) { for (s32 overlayColor = eOverlayTextColours_First; overlayColor < eOverlayTextColours_MAX; overlayColor++) { eHUD_COLOURS const c_testHudColor = ConvertToHudColour((eOverlayTextColours)overlayColor); if (c_testHudColor == c_hudColor) { return (eOverlayTextColours)overlayColor; } } return eOverlayTextColours_White; } CRGBA CTextFormat::ConvertToRGBA( eOverlayTextColours const overlayColour, float const * const alphaOverride ) { CRGBA outputColour( CHudColour::GetRGBA( ConvertToHudColour( overlayColour ) ) ); int const c_alpha = alphaOverride ? (int)Clamp( 255.f * (*alphaOverride), 0.f, 255.f ) : outputColour.GetAlpha(); outputColour.SetAlpha( c_alpha ); return outputColour; } s32 CTextFormat::GetGameDisplayNameIndexOfOverlayFont( s32 const c_currentFont ) { switch (c_currentFont) { case FONT_STYLE_STANDARD: return 1; case FONT_STYLE_CURSIVE: return 2; case FONT_STYLE_CONDENSED: return 3; case FONT_STYLE_PRICEDOWN: return 4; default: break; } Assertf(0, "CTextFormat::GetGameDisplayNameIndexOfOverlayFont Font %d is used but we dont have a valid name index for it", c_currentFont); return 0; } // PURPOSE: Filters the given input font until we hit a valid font // PARAMS: // currentFont - The font to filter // c_incrementalFilter - If true, we increment until we find a valid font. If false we decrement. // pStringToDisplay - check each character in this string. If a font doesn't support all those characters then skip that font. // RETURNS: // s32 representing a valid entry in the font styles enum (See text.h) s32 CTextFormat::FilterOverlayFonts( s32 currentFont, bool const c_incrementalFilter, const char *pStringToDisplay ) { s32 const c_increment = c_incrementalFilter ? 1 : -1; #if GTA_REPLAY char16 wideStringToDisplay[CMontageText::MAX_MONTAGE_TEXT]; Utf8ToWide(wideStringToDisplay, pStringToDisplay, CMontageText::MAX_MONTAGE_TEXT); #else char16 wideStringToDisplay[PhonePhotoEditor::PHOTO_TEXT_MAX_LENGTH]; Utf8ToWide(wideStringToDisplay, pStringToDisplay, PhonePhotoEditor::PHOTO_TEXT_MAX_LENGTH); #endif bool bCheckAllCharactersInString = true; const s32 startingFont = currentFont; s32 pass = 0; bool bFontIsValid = false; while( !bFontIsValid ) { if (currentFont == startingFont) { // In pass 1, we'll check that all characters in the string are supported in the font. // If no valid font is found in pass 1 then do a second pass where we don't check the characters. // Hopefully pass 2 will always succeed. pass++; if (pass > 1) { bCheckAllCharactersInString = false; } } // Clamp and wrap-around the font style currentFont = ( currentFont >= FONT_MAX_FONT_STYLES ) ? FONT_STYLE_FIRST : ( currentFont < FONT_STYLE_FIRST ) ? (FONT_MAX_FONT_STYLES-1) : currentFont; if ( // Skip any fonts that have incomplete/invalid character sets.... ( currentFont == FONT_STYLE_LEADERBOARD || currentFont == FONT_STYLE_FIXED_WIDTH_NUMBERS || currentFont == FONT_STYLE_ROCKSTAR_TAG || currentFont == FONT_STYLE_CONDENSED_NOT_GAMERNAME || currentFont == FONT_MAX_FONT_STYLES ) || // ...or any fonts that re-map to the standard font style to avoid duplicate fonts ( currentFont != FONT_STYLE_STANDARD && CTextFormat::DoFontsShareMapping( FONT_STYLE_STANDARD, currentFont ) ) || // ...or any fonts that don't support all the characters in the string to be displayed ( bCheckAllCharactersInString && !DoAllCharactersExistInFont(currentFont, wideStringToDisplay, NELEM(wideStringToDisplay)) ) ) { currentFont += c_increment; } else { bFontIsValid = true; } } return currentFont; } bool CTextFormat::IsUnsignedInteger(const char *pString, u32 &ReturnInteger) { ReturnInteger = 0; s32 string_length = istrlen(pString); if (string_length == 0) { return false; } s32 char_loop = 0; while (char_loop < string_length) { s32 current_char = pString[char_loop] - '0'; if ((current_char >= 0) && (current_char <= 9)) { ReturnInteger *= 10; ReturnInteger += current_char; } else { return false; } char_loop++; } return true; } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CTextFormat::GetColourFromToken // PURPOSE: retrieves the correct hud colour for the token passed ///////////////////////////////////////////////////////////////////////////////////// Color32 CTextFormat::GetColourFromToken(char *pTokenText, bool *bFoundColour, const Color32 *pOriginalColour) { *bFoundColour = true; // assume we find a colour unless we get to end of function if ( (!strncmp("HUD_COLOUR_", pTokenText, 11)) || // check if the actual hud colour id is the token (!strncmp("HC_", pTokenText, 3)) ) // also HUD_COLOUR_ abbreviated to HC_ { char cFullColourTokenText[255]; if ((strlen(pTokenText) >= 3) && (!strncmp("HC_", pTokenText, 3))) { u32 StringAsInteger = 0; if (IsUnsignedInteger(&pTokenText[3], StringAsInteger)) { return CHudColour::GetRGBA(static_cast(StringAsInteger)); } sprintf(cFullColourTokenText, "HUD_COLOUR_%s", &pTokenText[3]); } else { safecpy(cFullColourTokenText, pTokenText, 255); } return CHudColour::GetRGBA(CHudColour::GetColourFromKey(cFullColourTokenText)); } if (*pTokenText == FONT_CHAR_TOKEN_COLOUR_RED) { return CHudColour::GetRGBA(HUD_COLOUR_RED); } if (*pTokenText == FONT_CHAR_TOKEN_COLOUR_GREEN) { return CHudColour::GetRGBA(HUD_COLOUR_GREEN); } if (*pTokenText == FONT_CHAR_TOKEN_COLOUR_BLUE) { return CHudColour::GetRGBA(HUD_COLOUR_BLUE); } if (*pTokenText == FONT_CHAR_TOKEN_COLOUR_WHITE) { return CHudColour::GetRGBA(HUD_COLOUR_WHITE); } if ( (*pTokenText == FONT_CHAR_TOKEN_COLOUR_GREY) || (*pTokenText == FONT_CHAR_TOKEN_COLOUR_FOREIGN_LANGUAGE_SUBTITLE) ) { return CHudColour::GetRGBA(HUD_COLOUR_MENU_GREY); } if (*pTokenText == FONT_CHAR_TOKEN_COLOUR_ORANGE) { return CHudColour::GetRGBA(HUD_COLOUR_ORANGE); } if (*pTokenText == FONT_CHAR_TOKEN_COLOUR_YELLOW) { return CHudColour::GetRGBA(HUD_COLOUR_YELLOW); } if (*pTokenText == FONT_CHAR_TOKEN_COLOUR_PURPLE) { return CHudColour::GetRGBA(HUD_COLOUR_PURPLE); } if (*pTokenText == FONT_CHAR_TOKEN_COLOUR_PINK) { return CHudColour::GetRGBA(HUD_COLOUR_PINK); } if (*pTokenText == FONT_CHAR_TOKEN_COLOUR_MENU) { return CHudColour::GetRGBA(HUD_COLOUR_MID_GREY_MP); } if (*pTokenText == FONT_CHAR_TOKEN_COLOUR_BLACK) { return CHudColour::GetRGBA(HUD_COLOUR_BLACK); } if (*pTokenText == FONT_CHAR_TOKEN_COLOUR_BLUEDARK) { return CHudColour::GetRGBA(HUD_COLOUR_BLUEDARK); } if (*pTokenText == FONT_CHAR_TOKEN_COLOUR_STANDARD) { return (*pOriginalColour); // revert back to the original colour } if (*pTokenText == FONT_CHAR_TOKEN_SCRIPT_VARIABLE) { return CHudColour::GetRGBA(HUD_COLOUR_SCRIPT_VARIABLE); } if (*pTokenText == FONT_CHAR_TOKEN_SCRIPT_VARIABLE_2) { return CHudColour::GetRGBA(HUD_COLOUR_SCRIPT_VARIABLE_2); } if (*pTokenText == FONT_CHAR_TOKEN_COLOUR_FRIENDLY) { return CHudColour::GetRGBA(HUD_COLOUR_FRIENDLY); } // we didnt find a colour *bFoundColour = false; return Color32(0,0,0,0); } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CTextFormat::GetSpecificToken // PURPOSE: retrieves the correct id for specific text tokens ///////////////////////////////////////////////////////////////////////////////////// s32 CTextFormat::GetSpecificToken(char *pTokenText) { if (*pTokenText == FONT_CHAR_TOKEN_NEWLINE) return FONT_CHAR_TOKEN_ID_NEWLINE; if ((!::strcmpi(pTokenText, FONT_CHAR_TOKEN_BOLD)) || (*pTokenText == FONT_CHAR_TOKEN_HIGHLIGHT)) return FONT_CHAR_TOKEN_ID_BOLD; if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_ITALIC)) return FONT_CHAR_TOKEN_ID_ITALIC; if ((!::strcmpi(pTokenText, FONT_CHAR_TOKEN_WANTED_STAR)) || (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_WANTED_STAR_ABBRIEVIATED))) return FONT_CHAR_TOKEN_ID_WANTED_STAR; if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_ROCKSTAR_LOGO)) return FONT_CHAR_TOKEN_ID_ROCKSTAR_LOGO; if (!::strcmpi(pTokenText, FONT_CHAR_TOKEN_NON_RENDERABLE_TEXT)) return FONT_CHAR_TOKEN_ID_NON_RENDERABLE_TEXT; if (*pTokenText == FONT_CHAR_TOKEN_DIALOGUE) return FONT_CHAR_TOKEN_ID_DIALOGUE; // no token found, return invalid return FONT_CHAR_TOKEN_INVALID; } void CTextFormat::ConvertToGenericAxisIcons( IconList &Icons ) { if(Icons.size() == 2) { if(Icons[0].m_IconId == FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_ALL && Icons[1].m_IconId == FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_ALL) { Icons.clear(); SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_ALL); } else if(Icons[0].m_IconId == FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_LEFTRIGHT && Icons[1].m_IconId == FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_LEFTRIGHT) { Icons.clear(); SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_LEFTRIGHT); } else if(Icons[0].m_IconId == FONT_CHAR_TOKEN_ID_CONTROLLER_DPAD_UPDOWN && Icons[1].m_IconId == FONT_CHAR_TOKEN_ID_CONTROLLER_LSTICK_UPDOWN) { Icons.clear(); SafeAddIconToList(Icons, FONT_CHAR_TOKEN_ID_CONTROLLER_UPDOWN); } } } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CTextFormat::GetDpadIcon // PURPOSE: retrieves the dpad icon type for a source. // PARAMS: source - the source to return the icon for. ///////////////////////////////////////////////////////////////////////////////////// CTextFormat::DpadIconDirection CTextFormat::GetDpadIconDirection( const ioSource& source ) { if(source.m_Device != IOMS_UNDEFINED STEAM_CONTROLLER_ONLY(&& source.m_Device != IOMS_GAME_CONTROLLED)) { switch(ioMapper::ConvertParameterToMapperValue(source.m_Device, source.m_Parameter)) { case LUP: case LUP_INDEX: #if __WIN32PC case IOM_POV1_UP: case IOM_POV2_UP: case IOM_POV3_UP: case IOM_POV4_UP: #endif // __WIN32PC return DPAD_UP; case LDOWN: case LDOWN_INDEX: #if __WIN32PC case IOM_POV1_DOWN: case IOM_POV2_DOWN: case IOM_POV3_DOWN: case IOM_POV4_DOWN: #endif // __WIN32PC return DPAD_DOWN; case LLEFT: case LLEFT_INDEX: #if __WIN32PC case IOM_POV1_LEFT: case IOM_POV2_LEFT: case IOM_POV3_LEFT: case IOM_POV4_LEFT: #endif // __WIN32PC return DPAD_LEFT; case LRIGHT: case LRIGHT_INDEX: #if __WIN32PC case IOM_POV1_RIGHT: case IOM_POV2_RIGHT: case IOM_POV3_RIGHT: case IOM_POV4_RIGHT: #endif // __WIN32PC return DPAD_RIGHT; default: break; } } return DPAD_NONE; } // PURPOSE: get the font name based on the style enum void CTextFormat::GetFontName(GString& fontName, int style) { switch (style) { case FONT_STYLE_STANDARD: { fontName = "$Font2"; break; } case FONT_STYLE_CURSIVE: { fontName = "$Font5"; break; } case FONT_STYLE_ROCKSTAR_TAG: { fontName = "$RockstarTAG"; break; } case FONT_STYLE_LEADERBOARD: { fontName = "$GTAVLeaderboard"; break; } case FONT_STYLE_CONDENSED: { fontName = "$Font2_cond"; break; } case FONT_STYLE_FIXED_WIDTH_NUMBERS: { fontName = "$FixedWidthNumbers"; break; } case FONT_STYLE_CONDENSED_NOT_GAMERNAME: { fontName = "$Font2_cond_NOT_GAMERNAME"; break; } case FONT_STYLE_PRICEDOWN: { fontName = "$gtaCash"; break; } case FONT_STYLE_TAXI: { fontName = "$Taxi_font"; break; } default: { Warningf( "CTextFormat::GetFontName - Invalid font style (%d) set", style ); fontName = "$Font2"; // default to standard font break; } } } bool CTextFormat::DoFontsShareMapping( int styleA, int styleB ) { GString unmappedNameA; GetFontName( unmappedNameA, styleA ); GString unmappedNameB; GetFontName( unmappedNameB, styleB ); return CScaleformMgr::DoFontsShareMapping( unmappedNameA.ToCStr(), unmappedNameB.ToCStr() ); } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CTextFormat::SetupFontParams // PURPOSE: sets up all the Scaleform font params in one function ///////////////////////////////////////////////////////////////////////////////////// void CTextFormat::SetupFontParams(GFxDrawTextManager::TextParams& params, const CTextLayout *pFormatDetails, float fPosition, Vector2 *LeftRight) { if (!CText::AreScaleformFontsInitialised()) { return; } GetFontName(params.FontName, pFormatDetails->Style); params.FontSize = pFormatDetails->Scale.y; params.Multiline = true; params.WordWrap = true; params.TextColor = GColor(pFormatDetails->Color.GetRed(),pFormatDetails->Color.GetGreen(),pFormatDetails->Color.GetBlue(),pFormatDetails->Color.GetAlpha()); params.Leading = pFormatDetails->iLeading; if (pFormatDetails->Orientation == FONT_CENTRE) { params.HAlignment = GFxDrawText::Align_Center; // we need to adjust so that the centre is always around the X pos we pass in, but keeping the // values set via the wrap positions float fHalfDiffBetweenSpace = ((pFormatDetails->WrapEnd - pFormatDetails->WrapStart) * 0.5f); float fAmountToAdjust = (pFormatDetails->WrapStart + fHalfDiffBetweenSpace) - fPosition; LeftRight->x = pFormatDetails->WrapStart-fAmountToAdjust; LeftRight->y = pFormatDetails->WrapEnd-fAmountToAdjust; } else if (pFormatDetails->Orientation == FONT_LEFT) { params.HAlignment = GFxDrawText::Align_Left; LeftRight->x = fPosition; LeftRight->y = pFormatDetails->WrapEnd; } else { params.HAlignment = GFxDrawText::Align_Right; float fOffset = 1.0f; #if SUPPORT_MULTI_MONITOR if(GRCDEVICE.GetMonitorConfig().isMultihead()) { fOffset = 0.6666f; } #endif // SUPPORT_MULTI_MONITOR LeftRight->x = pFormatDetails->WrapStart * fOffset; LeftRight->y = pFormatDetails->WrapEnd; } if (pFormatDetails->RenderUpwards) { params.VAlignment = GFxDrawText::VAlign_Bottom; } else { params.VAlignment = GFxDrawText::VAlign_Top; } return; } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CTextFormat::GetCharacterHeight // PURPOSE: returns the character height if the character was rendered in the current // viewport ///////////////////////////////////////////////////////////////////////////////////// float CTextFormat::GetCharacterHeight(const CTextLayout& FormatDetails) { float fScreenHeight = (float)GRCDEVICE.GetHeight(); // use device resolution if (CSystem::IsThisThreadId(SYS_THREAD_RENDER)) // if on RT we can use the current viewport { const grcViewport *pCurrentViewport = grcViewport::GetCurrent(); if (pCurrentViewport) fScreenHeight = (float)pCurrentViewport->GetHeight(); } return ( (FormatDetails.Scale.y + FormatDetails.iLeading) / fScreenHeight); } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CTextFormat::GetNumberLines // PURPOSE: returns the number of lines that would be used if the text was to be // rendered inside the current viewport ///////////////////////////////////////////////////////////////////////////////////// s32 CTextFormat::GetNumberLines(const CTextLayout& FormatDetails, float fXPos, float /*fYPos*/, const char *pStringToRender) { char cTextString[MAX_CHARS_FOR_TEXT_STRING]; s32 iNumberOfLines = 0; if (!CText::AreScaleformFontsInitialised()) { return iNumberOfLines; } float fWidth = (float)VideoResManager::GetUIWidth(); float fHeight = (float)VideoResManager::GetUIHeight(); #if SUPPORT_MULTI_MONITOR const GridMonitor &mon = GRCDEVICE.GetMonitorConfig().getLandscapeMonitor(); fWidth = (float)mon.getWidth(); fHeight = (float)mon.getHeight(); #endif // SUPPORT_MULTI_MONITOR Vector2 vScreenSpace = Vector2(fWidth, fHeight); // use device resolution if (CSystem::IsThisThreadId(SYS_THREAD_RENDER)) // if on RT we can use the current viewport { const grcViewport *pCurrentViewport = grcViewport::GetCurrent(); AssertMsg(pCurrentViewport, "CTextFormat - Invalid viewport"); if (pCurrentViewport) vScreenSpace = Vector2((float)pCurrentViewport->GetWidth(), (float)pCurrentViewport->GetHeight()); } // // setup the text params based on our current font layout: // CTextLayout LocalFormatDetails = FormatDetails; if (FormatDetails.bAdjustForNonWidescreen) // adjust position, here - scale will be adjusted later in the render { fXPos /= NON_WIDESCREEN_SCALER; LocalFormatDetails.WrapStart /= NON_WIDESCREEN_SCALER; // fix for the wrap issue mentioned in LocalFormatDetails.WrapEnd /= NON_WIDESCREEN_SCALER; } Vector2 vFinalPos(0,0); bool bUsingHtml; GFxDrawTextManager::TextParams params; SetupFontParams(params, &LocalFormatDetails, fXPos, &vFinalPos); // // Get final formatted string: // ParseToken(pStringToRender, cTextString, LocalFormatDetails.TextColours, &LocalFormatDetails.Color, params.FontSize, &bUsingHtml, NELEM(cTextString), true); // go through, find and replace colour tokens with HTML tags if (cTextString[0] != 0) { float fWidth = (vFinalPos.y-vFinalPos.x)*vScreenSpace.x; GFxDrawTextManager* drawText = CScaleformMgr::GetMovieMgr()->GetDrawTextManager(); iNumberOfLines = drawText->GetNumberLines( cTextString, fWidth, ¶ms, bUsingHtml); } return (iNumberOfLines); } // FontStyle should be taken from the enum in text.h e.g. FONT_STYLE_STANDARD bool CTextFormat::DoesCharacterExistInFont(int FontStyle, UInt16 characterCode) { if (!CText::AreScaleformFontsInitialised()) { return false; } bool bCharacterExists = false; GString fontName; GetFontName(fontName, FontStyle); GFxDrawTextManager* drawText = CScaleformMgr::GetMovieMgr()->GetDrawTextManager(); bCharacterExists = drawText->DoesCharacterExistInFont(fontName.ToCStr(), 0, characterCode); return bCharacterExists; } // FontStyle should be taken from the enum in text.h e.g. FONT_STYLE_STANDARD bool CTextFormat::DoAllCharactersExistInFont(s32 fontStyle, const char16 *pWideString, s32 maxStringLength) { if (pWideString) { for (int i=0; i cOriginalTextString[iCharCount+2] == 'r' && cOriginalTextString[iCharCount+3] == '/' && cOriginalTextString[iCharCount+4] == '>') { // we found a line break, so break out iCharCount += 5; // skip past the
break; } } cPartTextString[iNewStringCharCount++] = cOriginalTextString[iCharCount++]; // copy over each char } cPartTextString[iNewStringCharCount] = '\0'; // terminate the part string char *pString = &cPartTextString[0]; if (pString[0] != 0) { GFxDrawTextManager* drawText = CScaleformMgr::GetMovieMgr()->GetDrawTextManager(); GSizeF stringSize; if (bUsingHtml) stringSize = drawText->GetHtmlTextExtent(pString, 0, ¶ms); else stringSize = drawText->GetTextExtent(pString, 0, ¶ms); float fNewWidth = (stringSize.Width / fScreenWidth); if (fNewWidth > fStringWidth) { fStringWidth = fNewWidth; } } } return (fStringWidth + 0.001f); } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CTextFormat::FilterOutTokensFromString // PURPOSE: removes any tokens from the string passed in ///////////////////////////////////////////////////////////////////////////////////// void CTextFormat::FilterOutTokensFromString(char *String) { char buf[256]; safecpy( buf, String, sizeof(buf) ); char *pCharacters = &buf[0]; s32 i=0; while(*pCharacters != 0) { if( *pCharacters == FONT_CHAR_TOKEN_DELIMITER ) { pCharacters++; while(*pCharacters != FONT_CHAR_TOKEN_DELIMITER && *pCharacters != FONT_CHAR_END) { pCharacters++; } } else { String[i++] = *pCharacters; } pCharacters++; } // end of string: String[i] = char(0); } bool CTextFormat::IsUnsupportedSpace(char16 c) { return c == 0x3000 || // Ideographic c == 0x205F || // Medium Mathematical c == 0x202F || // Narrow No-Break c == 0x200B || // Zero Width c == 0x200A || // Hair c == 0x2009 || // Thin c == 0x2008 || // Punctuation c == 0x2007 || // Figure c == 0x2006 || // Six-Per-Em c == 0x2005 || // Four-Per-Em c == 0x2004 || // Three-Per-Em c == 0x2003 || // Em Space c == 0x2002 || // En Space c == 0x2001 || // Em Quad c == 0x2000 || // En Quad c == 0x1680 || // Ogham Mark c == 0x00A0; // No Break } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CTextFormat::EndFrame // PURPOSE: at the end of each frame, we can go through and delete any unused items // from the buffer - must be called in the synced safe area ///////////////////////////////////////////////////////////////////////////////////// void CTextFormat::EndFrame() { } ///////////////////////////////////////////////////////////////////////////////////// // NAME: CTextFormat::FlushFontBuffer // PURPOSE: flushes the current font buffer to the screen ///////////////////////////////////////////////////////////////////////////////////// void CTextFormat::FlushFontBuffer() { using namespace CachedTextDrawing; GRC_ALLOC_SCOPE_AUTO_PUSH_POP() PF_FUNC(Text_Flush); if (!CText::AreScaleformFontsInitialised()) // font system not initialised { return; } const unsigned rti = g_RenderThreadIndex; if (DrawCommands[rti].IsEmpty()) { return; // nothing to draw } DIAG_CONTEXT_MESSAGE("Drawing scaleform text"); const grcViewport *pCurrentViewport = grcViewport::GetCurrent(); AssertMsg(pCurrentViewport, "CTextFormat - Invalid viewport"); if (pCurrentViewport) { Vector2 vViewportSize = Vector2((float)pCurrentViewport->GetWidth(), (float)pCurrentViewport->GetHeight()); GViewport vp((s32)vViewportSize.x, (s32)vViewportSize.y, 0, 0, (s32)vViewportSize.x, (s32)vViewportSize.y, 0); GFxDrawTextManager* drawText = CScaleformMgr::GetMovieMgr()->GetDrawTextManager(); CShaderLib::SetGlobalEmissiveScale(1.0f, true); drawText->BeginDisplay(vp); int numDrawCommands = DrawCommands[rti].GetCount(); for (s32 iCount = 0; iCount < numDrawCommands; iCount++) { sDrawCommand& drawCmd = DrawCommands[rti][iCount]; FastAssert(drawCmd.iTextCacheIndex != sCache::INVALID_ENTRY); sCache::Entry& cacheEntry = GfxDrawTextCache.Cache[drawCmd.iTextCacheIndex]; GFxDrawText& gfxText = *cacheEntry.pText; gfxText.SetBackgroundColor(drawCmd.background ? drawCmd.bgcolor : GColor::Alpha0); gfxText.SetBorderColor(drawCmd.backgroundOutline ? drawCmd.bgcolor : GColor::Alpha0); // if we are in 4:3 we need to adjust the aspect ratio of the text // doing it this way round (rather than squashing it in 16:9) creates // a better quality font in 16:9 which is our main target display if (drawCmd.adjustForNonWidescreen) // adjust the scale here, position was done earlier { GFxDrawText::Matrix scaleMatrix; scaleMatrix.AppendScaling(NON_WIDESCREEN_SCALER, 1.0f); gfxText.SetMatrix(scaleMatrix); } GRectF origRect = gfxText.GetRect(); GRectF offsetRect = origRect; offsetRect.Offset(drawCmd.vPosition.x, drawCmd.vPosition.y); gfxText.SetRect(offsetRect); // Set the text filters (shadow / outline) since these apparently don't cause a re-format if (drawCmd.fShadow != 0.0f) { GFxDrawText::Filter filter(GFxDrawText::Filter_DropShadow); filter.DropShadow.Strength = drawCmd.fShadow * 2000.0f; filter.DropShadow.Color = GColor(0,0,0, drawCmd.dropalpha).ToColor32(); filter.DropShadow.Angle = 65.0f; filter.DropShadow.Distance = 1.0f; gfxText.SetFilters(&filter, 1); } else if (drawCmd.outline) { GFxDrawText::Filter filter(GFxDrawText::Filter_Glow); filter.Glow.Strength = 2000.0f; filter.Glow.BlurX = 1.5f; filter.Glow.BlurY = 1.5f; filter.DropShadow.Color = GColor(0,0,0, drawCmd.dropalpha).ToColor32(); if(drawCmd.outlineCut) { filter.SetKnockOut(); } gfxText.SetFilters(&filter, 1); } else { gfxText.ClearFilters(); } const bool c_scissorRequired = (drawCmd.scissor.Right != 0.0f && drawCmd.scissor.Bottom != 0.0f); if (c_scissorRequired) { // setup the scissor GRCDEVICE.SetScissor((int)(drawCmd.scissor.Left * vViewportSize.x), (int)(drawCmd.scissor.Top * vViewportSize.y), (int)(drawCmd.scissor.Right * vViewportSize.x), (int)(drawCmd.scissor.Bottom * vViewportSize.y)); } #if RSG_PC if (drawCmd.StereoFlag == 1) { CShaderLib::SetReticuleDistTexture(true); CShaderLib::SetStereoParams(Vector4(0,0,0,1)); } else if (drawCmd.StereoFlag == 2) { CShaderLib::SetStereoParams(Vector4(0,1,0,0)); } #endif gfxText.Display(); #if RSG_PC if (drawCmd.StereoFlag != 0) CShaderLib::SetStereoParams(Vector4(0,0,0,0)); #endif if (c_scissorRequired) { GRCDEVICE.DisableScissor(); // turn off the scissor } gfxText.SetRect(origRect); GfxDrawTextCache.ReleaseEntry(drawCmd.iTextCacheIndex); } DrawCommands[rti].Reset(); drawText->EndDisplay(); } } #if __BANK static const char* testLine[10] = {"ABCDEFGHIJ", "KLMNOPQRST", "UVWXYZ", "abcdefghij", "klmnopqrst", "uvwxyz", "0123456789", "!@#$%^&*()", "_+{}|[]\\:""", ";'<>?,./" }; static bool fontTestRender = false; static s32 fontType = 0; static Color32 col(0,0,0,255); static float fontSize = 32.90f; static s32 style = GFxDrawText::Normal; static bool underline = false; static s32 aaMode = GFxDrawText::AA_Animation; static GRectF rect[10]; ///////////////////////////////////////////////////////////////////////////////////// // NAME: CTextFormat::CreateBankWidgets() // PURPOSE: creates the bank widget ///////////////////////////////////////////////////////////////////////////////////// void CTextFormat::CreateBankWidgets() { static bool bBankCreated = false; bkBank *bank = BANKMGR.FindBank(UI_DEBUG_BANK_NAME); if(!Verifyf(bank, "CTextFormat::CreateBankWidgets - Trying to use %s before it was made.", UI_DEBUG_BANK_NAME)) return; Vector2 vRes = Vector2((float)VideoResManager::GetUIWidth(), (float)VideoResManager::GetUIHeight()); // use device resolution //safecpy(ms_cDebugScriptedString, "WTD_BA_0", NELEM(ms_cDebugScriptedString)); bank->AddText("Debug Scripted Text", ms_cDebugScriptedString, sizeof(ms_cDebugScriptedString), false); bank->AddToggle("Text Ids only", &TheText.bShowTextIdOnly); bank->AddToggle("Render Text to Log", &bOutputAllTextRenderToLog); bank->AddSlider("Text icon size scaler", &fTextIconScaler, -4.0f, 4.0f, 0.02f); bank->AddSlider("Text icon size offset", &fTextIconOffset, -4.0f, 4.0f, 0.02f); if ((!bBankCreated)) { bank->PushGroup("Scaleform Font Test"); bank->AddToggle("fontTestRender", &fontTestRender); bank->AddSlider("Font Type",&fontType,0,1,1); bank->AddColor("Font color",&col); bank->AddSlider("Font Size",&fontSize,0.0f,50.0f,0.1f); bank->AddSlider("Font Style",&style,0,3,1); bank->AddSlider("AA Mode",&aaMode,0,1,1); bank->AddToggle("Underline", &underline); rect[0].SetRect(128.0f, 128.0f, vRes.x, vRes.y); rect[1].SetRect(128.0f, 128.0f, vRes.x, vRes.y); rect[2].SetRect(128.0f, 128.0f, vRes.x, vRes.y); rect[3].SetRect(128.0f, 128.0f, vRes.x, vRes.y); rect[4].SetRect(128.0f, 128.0f, vRes.x, vRes.y); rect[5].SetRect(128.0f, 128.0f, vRes.x, vRes.y); rect[6].SetRect(128.0f, 128.0f, vRes.x, vRes.y); rect[7].SetRect(128.0f, 128.0f, vRes.x, vRes.y); rect[8].SetRect(128.0f, 128.0f, vRes.x, vRes.y); rect[9].SetRect(128.0f, 128.0f, vRes.x, vRes.y); bank->AddSlider("Rect 0 Left",&rect[0].Left,0.0f,vRes.x,1.0f); bank->AddSlider("Rect 0 Top",&rect[0].Top,0.0f,vRes.y,1.0f); bank->AddSlider("Rect 0 Right",&rect[0].Right,0.0f,vRes.x,1.0f); bank->AddSlider("Rect 0 Bottom",&rect[0].Bottom,0.0f,vRes.y,1.0f); bank->AddSlider("Rect 1 Left",&rect[1].Left,0.0f,vRes.x,1.0f); bank->AddSlider("Rect 1 Top",&rect[1].Top,0.0f,vRes.y,1.0f); bank->AddSlider("Rect 1 Right",&rect[1].Right,0.0f,vRes.x,1.0f); bank->AddSlider("Rect 1 Bottom",&rect[1].Bottom,0.0f,vRes.y,1.0f); bank->AddSlider("Rect 2 Left",&rect[2].Left,0.0f,vRes.x,1.0f); bank->AddSlider("Rect 2 Top",&rect[2].Top,0.0f,vRes.y,1.0f); bank->AddSlider("Rect 2 Right",&rect[2].Right,0.0f,vRes.x,1.0f); bank->AddSlider("Rect 2 Bottom",&rect[2].Bottom,0.0f,vRes.y,1.0f); bank->AddSlider("Rect 3 Left",&rect[3].Left,0.0f,vRes.x,1.0f); bank->AddSlider("Rect 3 Top",&rect[3].Top,0.0f,vRes.y,1.0f); bank->AddSlider("Rect 3 Right",&rect[3].Right,0.0f,vRes.x,1.0f); bank->AddSlider("Rect 3 Bottom",&rect[3].Bottom,0.0f,vRes.y,1.0f); bank->AddSlider("Rect 4 Left",&rect[4].Left,0.0f,vRes.x,1.0f); bank->AddSlider("Rect 4 Top",&rect[4].Top,0.0f,vRes.y,1.0f); bank->AddSlider("Rect 4 Right",&rect[4].Right,0.0f,vRes.x,1.0f); bank->AddSlider("Rect 4 Bottom",&rect[4].Bottom,0.0f,vRes.y,1.0f); bank->AddSlider("Rect 5 Left",&rect[5].Left,0.0f,vRes.x,1.0f); bank->AddSlider("Rect 5 Top",&rect[5].Top,0.0f,vRes.y,1.0f); bank->AddSlider("Rect 5 Right",&rect[5].Right,0.0f,vRes.x,1.0f); bank->AddSlider("Rect 5 Bottom",&rect[5].Bottom,0.0f,vRes.y,1.0f); bank->AddSlider("Rect 6 Left",&rect[6].Left,0.0f,vRes.x,1.0f); bank->AddSlider("Rect 6 Top",&rect[6].Top,0.0f,vRes.y,1.0f); bank->AddSlider("Rect 6 Right",&rect[6].Right,0.0f,vRes.x,1.0f); bank->AddSlider("Rect 6 Bottom",&rect[6].Bottom,0.0f,vRes.y,1.0f); bank->AddSlider("Rect 7 Left",&rect[7].Left,0.0f,vRes.x,1.0f); bank->AddSlider("Rect 7 Top",&rect[7].Top,0.0f,vRes.y,1.0f); bank->AddSlider("Rect 7 Right",&rect[7].Right,0.0f,vRes.x,1.0f); bank->AddSlider("Rect 7 Bottom",&rect[7].Bottom,0.0f,vRes.y,1.0f); bank->AddSlider("Rect 8 Left",&rect[8].Left,0.0f,vRes.x,1.0f); bank->AddSlider("Rect 8 Top",&rect[8].Top,0.0f,vRes.y,1.0f); bank->AddSlider("Rect 8 Right",&rect[8].Right,0.0f,vRes.x,1.0f); bank->AddSlider("Rect 8 Bottom",&rect[8].Bottom,0.0f,vRes.y,1.0f); bank->AddSlider("Rect 9 Left",&rect[9].Left,0.0f,vRes.x,1.0f); bank->AddSlider("Rect 9 Top",&rect[9].Top,0.0f,vRes.y,1.0f); bank->AddSlider("Rect 9 Right",&rect[9].Right,0.0f,vRes.x,1.0f); bank->AddSlider("Rect 9 Bottom",&rect[9].Bottom,0.0f,vRes.y,1.0f); bank->PopGroup(); bBankCreated = true; } } void CTextFormat::FontTest() { if (!CText::AreScaleformFontsInitialised()) { return; } if( false == fontTestRender ) return; const grcViewport *pCurrentViewport = grcViewport::GetCurrent(); AssertMsg(pCurrentViewport, "CTextFormat - Invalid viewport"); Vector2 vViewportSize = Vector2((float)pCurrentViewport->GetWidth(), (float)pCurrentViewport->GetHeight()); GPtr fontTest[10]; GFxDrawTextManager* drawText = CScaleformMgr::GetMovieMgr()->GetDrawTextManager(); for(s32 i=0;i<10;i++) { GString str(testLine[i]); fontTest[i] = drawText->CreateText(); fontTest[i]->SetText(str); switch(fontType) { case 0: fontTest[i]->SetFont("$Font1"); break; case 1: default: fontTest[i]->SetFont("$Font2"); break; } fontTest[i]->SetColor(GColor(col.GetRed(),col.GetGreen(),col.GetBlue(),col.GetAlpha())); fontTest[i]->SetFontSize(fontSize); fontTest[i]->SetFontStyle((GFxDrawText::FontStyle)style); fontTest[i]->SetUnderline(underline); fontTest[i]->SetAAMode((GFxDrawText::AAMode)aaMode); GRectF actRect = rect[i]; actRect.Top += (float)i*fontSize; fontTest[i]->SetRect(actRect); } GViewport vp((s32)vViewportSize.x, (s32)vViewportSize.y, 0, 0, (s32)vViewportSize.x, (s32)vViewportSize.y, 0); drawText->BeginDisplay(vp); for (s32 i=0; i<10; i++) { fontTest[i]->Display(); } drawText->EndDisplay(); for(s32 i=0;i<10;i++) { fontTest[i]->Release(); } } #endif // __BANK bool CTextFormat::Icon::GetIconText(char* buffer, u32 bufferSize, const IconParams& ORBIS_ONLY(iconParams)) const { #if KEYBOARD_MOUSE_SUPPORT if(m_Type == MOUSE_ID) { switch(m_IconId) { case MOUSE_MOV_LEFT: formatf(buffer, bufferSize, BUTTON_KEY_FMT, FONT_CHAR_TOKEN_MOUSE_MOVE_LEFT); return true; case MOUSE_MOV_RIGHT: formatf(buffer, bufferSize, BUTTON_KEY_FMT, FONT_CHAR_TOKEN_MOUSE_MOVE_RIGHT); return true; case MOUSE_MOV_UP: formatf(buffer, bufferSize, BUTTON_KEY_FMT, FONT_CHAR_TOKEN_MOUSE_MOVE_UP); return true; case MOUSE_MOV_DOWN: formatf(buffer, bufferSize, BUTTON_KEY_FMT, FONT_CHAR_TOKEN_MOUSE_MOVE_DOWN); return true; case MOUSE_MOV_LR: formatf(buffer, bufferSize, BUTTON_KEY_FMT, FONT_CHAR_TOKEN_MOUSE_MOVE_LEFTRIGHT); return true; case MOUSE_MOV_UD: formatf(buffer, bufferSize, BUTTON_KEY_FMT, FONT_CHAR_TOKEN_MOUSE_MOVE_UPDOWN); return true; case MOUSE_MOV_ALL: formatf(buffer, bufferSize, BUTTON_KEY_FMT, FONT_CHAR_TOKEN_MOUSE_MOVE_ALL); return true; case MOUSE_WHL_UP: formatf(buffer, bufferSize, BUTTON_KEY_FMT, FONT_CHAR_TOKEN_MOUSE_WHEEL_MOVE_UP); return true; case MOUSE_WHL_DOWN: formatf(buffer, bufferSize, BUTTON_KEY_FMT, FONT_CHAR_TOKEN_MOUSE_WHEEL_MOVE_DOWN); return true; case MOUSE_WHL_UD: formatf(buffer, bufferSize, BUTTON_KEY_FMT, FONT_CHAR_TOKEN_MOUSE_WHEEL_MOVE_UPDOWN); return true; case MOUSE_LEFT: formatf(buffer, bufferSize, BUTTON_KEY_FMT, FONT_CHAR_TOKEN_MOUSE_BUTTON_LEFT); return true; case MOUSE_RIGHT: formatf(buffer, bufferSize, BUTTON_KEY_FMT, FONT_CHAR_TOKEN_MOUSE_BUTTON_RIGHT); return true; case MOUSE_MIDDLE: formatf(buffer, bufferSize, BUTTON_KEY_FMT, FONT_CHAR_TOKEN_MOUSE_BUTTON_MIDDLE); return true; case MOUSE_EXTRABTN1: formatf(buffer, bufferSize, BUTTON_KEY_FMT, FONT_CHAR_TOKEN_MOUSE_BUTTON_4); return true; case MOUSE_EXTRABTN2: formatf(buffer, bufferSize, BUTTON_KEY_FMT, FONT_CHAR_TOKEN_MOUSE_BUTTON_5); return true; case MOUSE_EXTRABTN3: formatf(buffer, bufferSize, BUTTON_KEY_FMT, FONT_CHAR_TOKEN_MOUSE_BUTTON_6); return true; case MOUSE_EXTRABTN4: formatf(buffer, bufferSize, BUTTON_KEY_FMT, FONT_CHAR_TOKEN_MOUSE_BUTTON_7); return true; case MOUSE_EXTRABTN5: formatf(buffer, bufferSize, BUTTON_KEY_FMT, FONT_CHAR_TOKEN_MOUSE_BUTTON_8); return true; default: Assertf(false, "Invalid mouse icon id %d\n", m_IconId); return false; } } else if(m_Type == KEYCODE_ID) { const CControl::KeyInfo& info = CControl::GetKeyInfo(m_IconId); Assertf(info.m_Icon != CControl::KeyInfo::INVALID_ICON || m_IconId == KEY_NULL, "Invalid key icon for keycode %u", m_IconId); // In the case of invalid, we will end up showing a key icon with '???'. if(info.m_Icon == CControl::KeyInfo::TEXT_ICON) { formatf(buffer, bufferSize, KEYBOARD_KEY_FMT "%s", info.m_Text); } // In the case of invalid, we will end up showing a key icon with '???'. else if(info.m_Icon == CControl::KeyInfo::LARGE_TEXT_ICON) { formatf(buffer, bufferSize, KEYBOARD_LARGE_KEY_FMT "%s", info.m_Text); } else if(info.m_Icon == CControl::KeyInfo::INVALID_ICON) { safecpy(buffer, UNMAPPED_ICON_TEXT, bufferSize); } else { formatf(buffer, bufferSize, KEYBOARD_ICON_FMT "%u", info.m_Icon); } return true; } else #endif // KEYBOARD_MOUSE_SUPPORT if(m_IconId != FONT_CHAR_TOKEN_INVALID) { u32 buttonId; if(m_IconId >= FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_NONE) { buttonId = m_IconId; #if RSG_ORBIS // Some scaleform clips automatically swap the X/O buttons on the playstation. When we do not want this to happen we have to swap then first. if(iconParams.m_AllowXOSwap && !CPauseMenu::GetMenuPreference(PREF_ACCEPT_IS_CROSS)) { if(buttonId == FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_A) { buttonId = FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_B; } else if(buttonId == FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_B) { buttonId = FONT_CHAR_TOKEN_ID_CONTROLLER_BUTTON_A; } } #endif // RSG_ORBIS buttonId -= FONT_CHAR_TOKEN_ID_CONTROLLER_UP; } else { buttonId = m_IconId; } formatf( buffer, bufferSize, BUTTON_KEY_FMT, buttonId ); return true; } else { return false; } }