Translator

Translator class is used for localization of dynamic text fields. Using the Translator use may translate the text from one language to another, control word wrapping and control right-to-left text rendering.

Installing Translator

To install Translator user should:

Translation of Text

Translation is performed through the Translate virtual function which is expected to fill in a buffer with a translated string. Developers usually override Translate method of this class.

The translation interface will be used to look up dynamic text in all of the text fields. For this reason, it is often wise to differentiate translatable text fields in the SWF file from the dynamic ones by prefixing the content with a special tag, such as a '$'. If this is done, translation implementation can translate all of the prefixed strings, while leaving the non-prefixed ones unchanged.

Scaleform translation interface can both receive and generate HTML formatted strings. By default, HTML tags are stripped out before calling Translate, but this behavior might be changed by returning bit Cap_ReceiveHtml set from GetCaps virtual function.

Translate method receives a pointer to TranslateInfo class. This class provides data exchange between Scaleform core and users code. Method TranslateInfo::GetKey() returns original text in UTF-8 encoding. Methods TranslateInfo::SetResult and TranslateInfo::SetResultHtml should be used to return plain and HTML translated texts respectively. These methods exist in two versions each for Unicode (UCS-2) and for UTF-8 encodings.

class TranslatorImpl : public Translator
{
    virtual unsigned GetCaps() const
    {
        // Kill ending new line that gets generated if HTML text field content is used to 
        // create a translation key.
        return Cap_StripTrailingNewLines;
    }

    virtual bool Translate(TranslateInfo* ptranslateInfo)
    {
        // Translate '$hi' into 'Hello!'. Users can perform string lookup 
 // and substitution here.
        const char* pkey = ptranslateInfo->GetKey();
        if (pkey[0] == '$')
        {
            String key(pkey);
            if (pkey == "$hi")
            {
                ptranslateInfo->SetResult(L"Hello!");
            }
        }
    }

};

// Use class when setting up a loader or a movie view.
Ptr<TranslatorImpl> ptranslator = *new TranslatorImpl();
loader.SetTranslator(ptranslator);

Controlling Word Wrapping

It is not a secret that different languages have different rules for word wrapping text. For most of the languages the algorithm is quite simple: it wraps at the nearest to the line's end space. However, Asian languages, such as Chinese, Japanese and Korean and others have their own rules.

Scaleform has built-in word wrapping rules for Chinese, Japanese and Korean languages, but it is also possible to implement any custom rules with using GFx::Translator API.

Word Wrapping for Asian Languages (Chinese, Japanese, Korean)

To use built-in Chinese, Japanese or Korean word wrapping it is necessary to inherit from GFx::Translator and to set WWMode to corresponding WWT_<> value (see below).

A constructor of the inherited class might have a parameter 'wwMode' that specifies the actual word wrapping mode. Or, the desired word wrapping mode can just be passed to the base class constructor. Possible values are:

enum WordWrappingTypes
{
    WWT_Default       = 0,
    WWT_Hyphenation,
    WWT_Custom, 

    WWT_Korean,
    WWT_Japanese,
    WWT_Chinese
};

WWT_Default Custom word wrapping is off, OnWordWrapping will not be invoked, the default simple algorithm to break at whitespaces will be used.

WWT_Hyphenation Turns on a simple hyphenation; for demo purposes only.

WWT_Chinese Turns on Chinese word wrapping rules.

WWT_Japanese Turns on Japanese prohibition rule.

WWT_Korean Turns on Korean-specific word wrapping rules.

WWT_Custom Turns on custom user-defined word wrapping. User should implement his own version of OnWordWrapping virtual method.

Here is an example of the Translator with the ability to specify the desired word wrapping rules:

#include "GFx_Loader.h"
using namespace Scaleform;

class TranslatorImpl : public GFx::Translator 
{ 
public: 
    // The 'wwMode' parameter can be one of the WWT_<> value.
    // Let say, by default we need Japanese word
    // wrapping here...
    TranslatorImpl(unsigned wwMode = GFx::Translator::WWT_Japanese) : 
      GFx::Translator(wwMode) {} 
}; 

To create this translator you need to provide word wrapping mode, for example, for Korean:

#include "GFx_Loader.h"
using namespace Scaleform;
....

GFx::Loader Loader; 
.... 
Loader.SetTranslator(Ptr<GFx::Translator>(*new TranslatorImpl(GFx::Translator::WWT_Korean)));

It is possible to change word wrapping mode later, by assigning appropriate value to WWMode member of the GFx::Translator class:

class TranslatorImpl : public GFx::Translator 
{ 
public: 
    .... 
    void SetWWMode(unsigned wwmode) const          
    { 
        WWMode = wwmode; 
    } 
};

Custom Word Wrapping

If the existing algorithms of word wrapping are not enough then it is possible to implement custom word wrapping algorithm. To do this, the GFx::Translator exposes the virtual method "OnWordWrapping" that should be overloaded:

virtual bool OnWordWrapping(LineFormatDesc* pdesc);

OnWordWrapping is a virtual method, a callback, which is invoked once a necessity of word wrapping for any text field is detected. This method is invoked only if custom word wrapping is turned on by using the GFx::Translator(wwMode) constructor, where wwMode should be set to GFx::Translator::WWT_Custom value:

class TranslatorImpl : public GFx::Translator 
{ 
public: 
    // Turning custom word wrapping on.
    TranslatorImpl() : 
      GFx::Translator(GFx::Translator::WWT_Custom) {} 

    // A callback that is invoked at word wrap event. 
    virtual bool OnWordWrapping(LineFormatDesc* pdesc); 
}; 

The parameter of the OnWordWrapping method is a pointer to the LineFormatDesc structure for calculating the word wrap position. The LineFormatDesc structure provides information of the line text to be formatted like the length of the text, text position etc:

struct LineFormatDesc
{
    const wchar_t*  pParaText;              
    UPInt           ParaTextLen;            
    const float*    pWidths;                
    UPInt           LineStartPos;           
    UPInt           NumCharsInLine;         
    float           VisibleRectWidth;       
    float           CurrentLineWidth;       
    float           LineWidthBeforeWordWrap;
    float           DashSymbolWidth;        
    enum                                    
    {
        Align_Left      = 0,
        Align_Right     = 1,
        Align_Center    = 2,
        Align_Justify   = 3
    };
    UInt8           Alignment;              

    UPInt           ProposedWordWrapPoint;  

    bool            UseHyphenation;         
};

Here is the description of the structure’s members:

const wchar_t* pParaText; [in] text of the current paragraph, wide-characters are used.

UPInt ParaTextLen; [in] length of the paragraph text, in characters.

UPInt NumCharsInLine; [in] number of characters currently in the line.

const float* pWidths; [in] an array of line widths, in pixels, before the character at the corresponding index. The size of the array is NumCharsInLine + 1. Note, this is not the array of character widths. For example, there is a line that contains three characters: ABC. The NumCharInLine will be equal 3, the size of the pWidths will be 4; the pWidth[0] will be always 0 (since there are no characters before the 'A'), the pWidth[1] will contain the width of 'A' symbol, pWidths[2] will contain the width of 'A' PLUS the width of 'B', and, finally, pWidths[3] will contain total width of the line (width of 'A' PLUS width of 'B' PLUS width of 'C').

UPInt LineStartPos; [in] the text position of the first character in the line. ParaTextLen[LineStartPos] might be used to get the value of this character.

float VisibleRectWidth; [in] width, in pixels, of the textfield rectangle (the inner text area, excluding borders). This width might be used in calculation of word wrapping position: the total width of line should not exceed this width.

float CurrentLineWidth; [in] current line width, in pixels.

float LineWidthBeforeWordWrap; [in] line width before the ProposedWordWrapPoint (see below), in pixels. For example, if line is "ABC DEF" and ProposedWordWrapPoint = 3 (space) then LineWidthBeforeWordWrap will contain the width of "ABC" (w/o space) part of the line.

float DashSymbolWidth; [in] supplementary member, width of the hyphen symbol, in pixels. It might be used to calculate hyphenation.

UInt8 Alignment [in] The alignment of the line. Can be one of the following values:

struct LineFormatDesc
{
    ....
    enum                                    
    {
        Align_Left      = 0,
        Align_Right     = 1,
        Align_Center    = 2,
        Align_Justify   = 3
    };
    ....
};

UPInt ProposedWordWrapPoint; [in,out] an index in the line of the proposed word wrap position. For example, if the line text is "ABC DEF" and only "ABC DE" fits in VisibleRectWidth then the ProposedWordWrapPoint will be equal to 3. Note, this is the index in line, not in text (pParaText), not in line. Use LineStartPos to calculate the proposed word wrapping position in the text. The user's OnWordWrapping method should change this member if it is necessary to change the word wrapping position according to custom rules.

bool UseHyphenation; [out] the user’s implementation of the OnWordWrapping method may set this to true to indicate to put hyphen symbol at the word wrapping position. This might be useful for implementing hyphenation. Note, all the members marked as "[in]" are used as input values only and shouldn't be modified. Only members markes as "[out]" or "[in,out]" might be modified.

How it Works

When Scaleform detects necessity of word wrapping and if custom word wrapping is turned on, then Scaleform fills the LineFormatDesc structure and passes it to OnWordWrapping translator's method. This structure already has so called "proposed word wrapping position". If the user decides that it is not necessary to perform any custom logic for this particular word wrap event she may return "false" from OnWordWrapping and that position will be used. Otherwise, the user should modify the proposed word wrapping position (stored in ProposedWordWrapPoint member) and the OnWordWrapping implementation should return "true".

Examples of Custom Word Wrapping Logic

Here is an example of the Translator with the custom word wrapping implementation. The OnWordWrapping method implements word wrapping at spaces, dots, commas and dashes.

class TranslatorImpl : public GFx::Translator 
{ 
public: 
    // Turning custom word wrapping on.
    TranslatorImpl() : 
        GFx::Translator(GFx::Translator::WWT_Custom) {} 

    // an example of custom word wrapping
    bool OnWordWrapping(LineFormatDesc* pdesc)
    {
       // check if we deal with Asian languages; if so, use
       // the default implementation.
       if (WWMode != WWT_Custom)
           return Translator::OnWordWrapping(pdesc);
        if (pdesc->NumCharsInLine == 0)
            return false; // skip empty lines
        // implementing word wrapping at spaces, dots, commas and dashes.
        // start from NumCharsInLine.
        // search for desired word breaker backward
        UPInt linePos = pdesc->NumCharsInLine - 1;
        UPInt textPos = pdesc->LineStartPos + linePos;
        for (SPInt tpos = SPInt(textPos), lpos = SPInt(linePos); 
            tpos >= 0 && lpos >= 0; 
            --tpos, --lpos)
        {
            int isSpace = SFiswspace(pdesc->pParaText[tpos]) ? 1 : 0;
            if (isSpace ||
                pdesc->pParaText[tpos] == '.' ||
                pdesc->pParaText[tpos] == ',' ||
                pdesc->pParaText[tpos] == '-')
            {
                // found our spacer;
                // check if width fits our textfield.
                // we will want to exclude the whitespace from the
                // widths, but include other spacers (so, they fit
                // the line).
                if (pdesc->pWidths[lpos + (1 - isSpace)] <= pdesc->VisibleRectWidth)
                {
                    // it fits, modify the ProposedWordWrapPoint and return true;
                    // otherwise, continue.
                    pdesc->ProposedWordWrapPoint = lpos + (1 - isSpace);
                    return true;
                }
            }
        }
        // nothing was found, return false.
        return false;
    }
}; 

An example of hyphenation:

class TranslatorImpl : public GFx::Translator 
{ 
public: 
    // Turning custom word wrapping on.
    TranslatorImpl() : 
        GFx::Translator(GFx::Translator::WWT_Custom | 
                        GFx::Translator::WWT_Hyphenation) {} 
 
    // an example of custom word wrapping with hyphenation
    bool OnWordWrapping(LineFormatDesc* pdesc)
    {
        if ((WWMode & WWT_Hyphenation))
        {
            if (pdesc->ProposedWordWrapPoint == 0)
                return false;
            const wchar_t* pstr = pdesc->pParaText + pdesc->LineStartPos;
            // determine if we need hyphenation or not. For simplicity,
            // we just will put dash only after vowels.
            UPInt hyphenPos = pdesc->NumCharsInLine;
            // check if the proposed word wrapping position is at the space.
            // if so, this will be the ending point in hyphenation position search.
            // Otherwise, will look for the position till the beginning of the line.
            // If we couldn't find appropriate position - just leave the proposed word
            // wrap point unmodified.
            UPInt endingHyphenPos = (SFiswspace(pstr[pdesc->ProposedWordWrapPoint - 1])) ? 
                pdesc->ProposedWordWrapPoint : 0;
            for (; hyphenPos > endingHyphenPos; --hyphenPos)
            {
                if (Text::WordWrapHelper::IsVowel(pstr[hyphenPos - 1]))
                {
                    // check if we have enough space for putting dash symbol
                    // we need to summarize all widths up to 
                    // hyphenPos + pdesc->DashSymbolWidth
                    // and this should be less than view rect width
                    float lineW = pdesc->pWidths[hyphenPos - 1];
                    lineW += pdesc->DashSymbolWidth;
                    if (lineW < pdesc->VisibleRectWidth)
                    {
                        // ok, looks like we can do hyphenation
                        pdesc->ProposedWordWrapPoint = hyphenPos;
                        pdesc->UseHyphenation = true;
                        return true;
                    }
                    else
                    {
                        // oops, we have no space for hyphenation mark
                        continue;
                    }
                    break;
                }
            }
        }
        return false;
    }
}; 

An example of Translator with handling Asian languages:

class TranslatorImpl : public GFx::Translator 
{ 
public: 
    // Turning custom word wrapping on.
    TranslatorImpl(unsigned wwMode) : 
        GFx::Translator(wwMode) {} 
 
    // an example of custom word wrapping with Asian langauges
    bool OnWordWrapping(LineFormatDesc* pdesc)
    {
        if (WWMode == WWT_Default)
            return false;
        if ((WWMode & (WWT_Asian | WWT_NoHangulWrap | WWT_Prohibition)) && 
             pdesc->NumCharsInLine > 0)
        {
            UPInt wordWrapPos = Text::WordWrapHelper::FindWordWrapPos
                (WWMode, 
                pdesc->ProposedWordWrapPoint, 
                pdesc->pParaText, pdesc->ParaTextLen, 
                pdesc->LineStartPos, 
                pdesc->NumCharsInLine);
            if (wordWrapPos != SF_MAX_UPINT)
            {
                pdesc->ProposedWordWrapPoint = wordWrapPos;
                return true;
            }
            return false;
        }
        return false;
    }
};