EAText is an advanced next-generation text characterization, layout, and font engine.
The biggest difference between EAText and other systems is that EAText supports all conceivable EA locales, including Western, Chinese, Japanese, Korean, and complex locales such as Thai, Hebrew, Hindi, and Arabic. This is the first time the problem of complex script layout has been universally solved within Electronic Arts and in doing so it should help lower one of the most significant barriers to multi-locale development. By using EAText, you automatically get support for about 25 languages in your game. This support includes basic layout and display, HTML/styled display, and interactive text editing. EAText additionally comes with a set of basic pipeline tools that can be used with EAText but can also be used with other systems due to their pipeline/engine-neutral design. Lastly, EAText and its associated packages follow standards such as Unicode, CSS, and XHTML, so expectations and behavior follow well-known conventions.
A summary of the primary features is as follows:
EAText supports layout and interactive editing in 28 languages from 9 scripts, including:
Arabic (bidirectional) French Italian Russian Chinese German Japanese (kanji, hiragana, katakana) Spanish Czech Greek Korean Swedish Danish Hebrew (bidirectional) Norwegian Thai (complex composition and breaking) Dutch Hindi (complex composition) Polish Tagalog English Hungarian Portuguese Turkish Finnish Icelandic Romanian Vietnamese
The following is a listing of the primary EAText modules in alphabetical order.
| Module |
Description |
| EAText.h/cpp |
Provides definitions of basic EAText data types and shared functions. |
| EATextBaseline.h/cpp | Implements text along a curve. |
| EATextCache.h/cpp | Implements a cache of rasterized glyphs for fast rendering. |
| EATextCompression.h/cpp | Implements a generic Unicode text compression algorithm that reduces the required size of text strings in game data. |
| EATextConfig.h | Provides the standard set of configurable defines for EAText. |
| EATextFont.h/cpp |
Defines and implements font interpretation and high level glyph representation. |
| EATextFontServer.h/cpp | Implements a font server, which is a repository of fonts that can be used at runtime. |
| EATextIterator.h/cpp | Implements walking through text and identifying paragraph breaks, sentence breaks, line break opportunities, word breaks, character breaks. |
| EATextScript.h/cpp |
Defines standard writing scripts defined by the Unicode standard and provides a few utility functions for working with script identifiers. |
| EATextStyle.h/cpp | Provides text style definitions, such as font weight, font families, etc. Also provides a StyleManager which manages an enumerated set of unique style definitions. |
| EATextTypesetter.h/cpp |
Defines and implements the EAText layout (typesetting) engine. |
| EATextUnicode.h/cpp |
Provides utility functionality related to the classification of text characters. |
The modules listed above have a somewhat layered hierarchy and thus you can use some of them without needing the others. For example, the EATextUnicode module is a somewhat independent module that the other modules use to do Unicode queries of various types. EATextTypesetter, on the other hand, is the core layout engine of EAText and is dependent on most of the rest of EAText.
Below is a flow diagram of the EAText runtime engine. Not all parts of the diagram need to be used by any given application. In summary, Runtime Data (on disk) consists of primarily text, style information, and fonts. To draw a block of text on the screen, text, text styles, fonts, and layout settings are fed to the Typesetter layout engine and a LineLayout is produced. A LineLayout has all the information needed to render a block of text on the screen. Typically, an application will convert the LineLayout to a GPU vertex buffer and shader (Glyph Mesh). The Renderer (or graphics driver) uses the Glyph Mesh plus the Glyph Cache (which stores individual glyphs on textures) to draw to the screen. An entity missing from the diagram below is a StyleManager, which manages Text Styles much like the Font Server manages Fonts.
An application that wants to use EAText will need to have the following (which is detailed in Graphics Engine Support):
A typical application that uses EAText will want to do the following:
Caveats:
Probably the best example usage of EAText can be found in one of the EAText adapter packages, such as EATextRNA. This package provides an adapter for EAText to the RNA graphics system and also provides a demonstration application which demonstrates many of the features of EAText. Another source for example usage is EATexts's own unit test code. The following provides some example usage of EAText that should at least give you a sense of it. The following consists of a number of simple but complete test applications. See the EAText/demo/ source files for more detailed examples.
Example of how to do various kinds of line breaking:
#include <EAText/EATextBreak.h>
#include <EAStdC/EAString.h>
int main(int, char**) { using namespace EA::Text; using namespace EA::StdC; { // Demo line breaking // Iterates all the breakable opportunities as well as forced breaks in the line. const char16_t pTest[] = L"a\x0300 b\x0301 c\x0302 d\x0303 efghijk"; TextRun tr(pTest, (uint32_t)Strlen(pTest)); LineBreakIterator bi(&tr, 1); uint32_t breakPos; do{ breakPos = bi.GetNextLineBreak(kLineBreakTypeEmergency); // Do something with breakPos }while(breakPos != tr.mnTextSize); } { // Demo character breaking // Iterates all the word divisions. const char16_t pTest[] = L"First line\nSecond line\nThird line"; TextRun tr(pTest, (uint32_t)Strlen(pTest)); CharacterBreakIterator bi(&tr, 1); uint32_t breakPos; do{ breakPos = bi.GetNextCharBreak(); // Do something with breakPos }while(breakPos != tr.mnTextSize); } { // Demo word breaking // Iterates all the word divisions. const char16_t pTest[] = L"First line\nSecond line\nThird line"; TextRun tr(pTest, (uint32_t)Strlen(pTest)); WordBreakIterator bi(&tr, 1); uint32_t breakPos; do{ breakPos = bi.GetNextWordBreak(); // Do something with breakPos }while(breakPos != tr.mnTextSize); } { // Demo sentence breaking // Iterates all the sentence divisions. const char16_t pTest[] = L"First sentence. Second sentence.\nThird sentence."; TextRun tr(pTest, (uint32_t)Strlen(pTest)); SentenceBreakIterator bi(&tr, 1); uint32_t breakPos; do{ breakPos = bi.GetNextSentenceBreak(); // Do something with breakPos }while(breakPos != tr.mnTextSize); } { // Demo sentence breaking // Iterates all the paragraph divisions. const char16_t pTest[] = L"First sentence. Second sentence.\nThird sentence."; TextRun tr(pTest, (uint32_t)Strlen(pTest)); SentenceBreakIterator bi(&tr, 1); uint32_t breakPos; do{ breakPos = bi.GetNextSentenceBreak(); // Do something with breakPos }while(breakPos != tr.mnTextSize); } return 0; }
Example of how to do Unicode queries:
#include <EAText/EATextUnicode.h>
int main(int, char**) { using namespace EA::Text; { // Example usage of IsSpace bool bNonBreakingSpace = IsSpace(' ', kSTNoBreak); bool bZeroWidthAndBreakable = IsSpace(' ', kSTZeroWidth | kSTBreak); bool bIsCRuntimeLibrarySpace = IsSpace(' ', kSTWidth | kSTControl); bool bIsAnySpace = IsSpace(' ', kSTAll, false); } { // Example usage of GetMirrorChar Char c1 = GetMirrorChar('['); // Returns ']' Char c2 = GetMirrorChar('a'); // Returns 'a' Char c3 = GetMirrorChar('$'); // Returns '$' } { // Example usage of IsCharUppercase bool b1 = IsCharUppercase('A'); // Returns true bool b2 = IsCharUppercase('a'); // Returns false bool b3 = IsCharUppercase('$'); // Returns false } { // Example of IsCharSTerm (sentence termination) bool b1 = IsCharSTerm('!'); // Returns true bool b2 = IsCharSTerm(' '); // Returns false bool b3 = IsCharSTerm('.'); // Returns true bool b4 = IsCharSTerm('a'); // Returns false bool b5 = IsCharSTerm(0xFF61); // Returns true (Japanese period). } return 0; }
Example of how to use a FontServer:
int main(int, char**) { using namespace EA::Text; using namespace EA::StdC; // Need to tell Font Fusion how to allocate memory. #ifdef FONTFUSIONMEMMANAGER_H // If using the FontFusion Fw2 package instead of using the regular commercial FontFusion version. FontFusionMemObject::SetAllocatorCallbacks(FF_Alloc, FF_Free, FF_Realloc); #endif // Need to tell EAText how to allocate memory. Any ICoreAllocator will do. CoreAllocatorMalloc coreAllocator; EA::Text::SetAllocator(&coreAllocator); { // Create and init a FontServer. Usually you would have one of these for an app. // Set the global font server to our FontServer, so anybody that wants to // find it can get it from a central location. FontServer fontServer; fontServer.Init(); EA::Text::SetFontServer(&fontServer); // You will probably want to tell the FontServer what the GlyphCache is if you // are going to be creating bitmapped fonts (BmpFont objects). // fontServer.SetDefaultGlyphCache(pSomeGlyphCache); // Add fonts to the FontServer. #if defined(EA_PLATFORM_WINDOWS) // Add some fonts from disk file sources. char16_t pFontDirectory[EA::IO::kMaxPathLength]; char16_t pFontPath[EA::IO::kMaxPathLength]; EA::Text::GetSystemFontDirectory(pFontDirectory, EA::IO::kMaxPathLength); // Add Arial normal Strcpy(pFontPath, pFontDirectory); Strcat(pFontPath, L"arial.ttf"); fontServer.AddFace(pFontPath); // Add Arial bold Strcpy(pFontPath, pFontDirectory); Strcat(pFontPath, L"arialb.ttf"); fontServer.AddFace(pFontPath); // Add Courier New normal Strcpy(pFontPath, pFontDirectory); Strcat(pFontPath, L"cour.ttf"); fontServer.AddFace(pFontPath); #endif // Add a font face from a memory image. EA::IO::MemoryStream* const pMemoryStream = new EA::IO::MemoryStream(const_cast
(gTrueTypeFont), kTrueTypeFontSize, true, false); pMemoryStream->AddRef(); fontServer.AddFace(pMemoryStream, kFontTypeOutline); pMemoryStream->Release(); // Add a Font directly to the FontServer. OutlineFont* pOutlineFont = new OutlineFont; pOutlineFont->AddRef(); pOutlineFont->Open(gTrueTypeFont, kTrueTypeFontSize); pOutlineFont->SetTransform(16); fontServer.AddFont(pOutlineFont); pOutlineFont->Release(); // Enumerate the existing font faces registered with the FontServer. FontDescription fd[16]; const uint32_t faceCount = fontServer.EnumerateFonts(fd, 16); (void)faceCount; // Do something with the FontDescription items. // Do a simple FontServer lookup. TextStyle ts; Strcpy(ts.mFamilyNameArray[0], L"Arial"); ts.mfSize = 16; ts.mStyle = kStyleItalic; Font* pFont = fontServer.GetFont(&ts, NULL); // GetFont AddRefs any fonts it returns. // Use pFont here. pFont->Release(); // Do a multi-font lookup, whereby the best matches are given. FontSelection fontSelection; Strcpy(ts.mFamilyNameArray[0], L"Arial"); Strcpy(ts.mFamilyNameArray[1], L"Courier New"); fontServer.GetFont(&ts, fontSelection); // There will be two fonts in fontSelection. // fontSelection will auto-release its fonts. // Shutdown EA::Text::SetFontServer(NULL); fontServer.Shutdown(); } return 0; }
Example of how to use a StyleManager:
int main(int, char**) { using namespace EA::Text; using namespace EA::StdC; // Need to tell EAText how to allocate memory. Any ICoreAllocator will do. CoreAllocatorMalloc coreAllocator; EA::Text::SetAllocator(&coreAllocator); StyleManager styleManager; TextStyle ts; // Create an Arial Italic 16, and give it a style id of 0x11111111 Strcpy(ts.mFamilyNameArray[0], L"Arial"); ts.mfSize = 16; ts.mStyle = kStyleItalic; ts.mSmooth = kSmoothNone; styleManager.AddStyle(0x11111111, ts); // Create an Arial 10, and give it a style id of 0x22222222 Strcpy(ts.mFamilyNameArray[0], L"Arial"); ts.mfSize = 10; ts.mStyle = kStyleNormal; ts.mSmooth = kSmoothNone; styleManager.AddStyle(0x22222222, ts); // Create an Russell Square 20 smoothed, and give it a style id of 0x33333333. // Make it so that Arial Unicode MS is a backup font for this style so that // Asian characters which are supported by Russell Square can be supported by Arial Unicode MS. Strcpy(ts.mFamilyNameArray[0], L"Russell Square"); Strcpy(ts.mFamilyNameArray[1], L"Arial Unicode MS"); ts.mfSize = 20; ts.mStyle = kStyleNormal; ts.mSmooth = kSmoothEnabled; styleManager.AddStyle(0x33333333, ts); // Create and register styles from a text description (usually part of game resource). const char* pMultiStyle = " Helv10(0x00000001){ \n" " font-family: \"Helvetica Neue\", arial; \n" " font-size: 10px; \n" " } \n" " \n" " Helv12{ \n" " font-family: \"Helvetica Neue\", arial; \n" " font-size: 12px; \n" " } \n" " \n" " Helv14(0x00000003) : Style1{ \n" " font-family: \"Helvetica Neue\", arial; \n" " font-size: 14px; \n" " } \n"; char pStyleName[24][32]; uint32_t pIdArray[24]; TextStyle ssCSSArray[24]; memset(pStyleName, 0, sizeof(pStyleName)); memset(pIdArray, 0, sizeof(pIdArray)); memset(ssCSSArray, 0, sizeof(ssCSSArray)); EA::Text::ParseStyleText(pMultiStyle, pStyleName, pIdArray, ssCSSArray, 24, &styleManager); // Retrieve a style based on its id. const TextStyle* pStyle = styleManager.GetStyle(0x22222222); (void)pStyle; return 0; }
Example of how to use Typesetter:
int main(int, char**) { using namespace EA::Text; using namespace EA::StdC; // Need to tell Font Fusion how to allocate memory. #ifdef FONTFUSIONMEMMANAGER_H // If using the FontFusion Fw2 package instead of using the regular commercial FontFusion version. FontFusionMemObject::SetAllocatorCallbacks(FF_Alloc, FF_Free, FF_Realloc); #endif // Need to tell EAText how to allocate memory. Any ICoreAllocator will do. CoreAllocatorMalloc coreAllocator; EA::Text::SetAllocator(&coreAllocator); // Create a GlyphCache for the rest of this app. Usually you create one of // these on startup and use it for the entire app. // Usually you will create a GlyphCache that is specialized to your // graphics system. But EAText provides a default memory cache that is // useful for testing and no-op graphics drivers. GlyphCache* const pGlyphCache = new GlyphCache_Memory; pGlyphCache->Init(8, 0); // Create a FontServer for the rest of this app. Usually you create one of // these on startup and use it for the entire app. FontServer fontServer; fontServer.Init(); EA::Text::SetFontServer(&fontServer); // You will probably want to tell the FontServer what the GlyphCache is if you // are going to be creating bitmapped fonts (BmpFont objects). fontServer.SetDefaultGlyphCache(pGlyphCache); // Add a Font directly to the FontServer. OutlineFont* pOutlineFont = new OutlineFont; pOutlineFont->AddRef(); pOutlineFont->Open(gTrueTypeFont, kTrueTypeFontSize); pOutlineFont->SetTransform(16); fontServer.AddFont(pOutlineFont); pOutlineFont->Release(); // Demonstrate LayoutSimple, which is useful for drawing (non-localized) debug text. // Note that we don't use the FontServer for this, but directly use a Font. { // Create a font. OutlineFont font; font.AddRef(); // We're using a refcounted object on the stack. So AddRef it. font.Open(gTrueTypeFont, kTrueTypeFontSize); font.SetTransform(12); // Layout some text. LineLayout lineLayout; LayoutSimple(L"Hello world", 11, 0, 0, &font, lineLayout); // Now you would take the LineLayout and write it to your vertex buffer system. // This is much like how the GlyphMesh class does things in the EATextRNA package. } // Demonstrate the high level layout functionality { Typesetter ts; LineLayout& lineLayout = ts.GetLineLayout(); TextStyle textStyle; Strcpy(textStyle.mFamilyNameArray[1], L"Arial"); textStyle.mfSize = 12; ts.LayoutLine(L"Hello World", 11, 150, 150, &textStyle); // Use lineLayout. Possibly give it to a GlyphMesh class which reads LineLayout info. // lineLayout has glyph info and metrics info. ts.LayoutParagraph(L"Four score and seven years ago our forefathers ...", 11, 150, 150, 200, 200, &textStyle, lineLayout); // Use lineLayout. Possibly give it to a GlyphMesh class which reads LineLayout info. } return 0; }
Example of how to use Typesetter to draw styled text:
(to-do)
Example of how to implement a text editor:
(to-do)
Example of how to make text follow a curve:
#include <EAText/EATextOutlineFont.h>
#include <EAText/EATextTypesetter.h>
#include <EAText/EATextBaseline.h>
int main(int, char**) { using namespace EA::Text; // Need to tell Font Fusion how to allocate memory. #ifdef FONTFUSIONMEMMANAGER_H // If using the FontFusion Fw2 package instead of using the regular commercial FontFusion version. FontFusionMemObject::SetAllocatorCallbacks(FF_Alloc, FF_Free, FF_Realloc); #endif // Need to tell EAText how to allocate memory. Any ICoreAllocator will do. CoreAllocatorMalloc coreAllocator; EA::Text::SetAllocator(&coreAllocator); // Demonstrate a line of text following a Bezier curve. { // Create a font. OutlineFont font; font.AddRef(); // We're using a refcounted object on the stack. So AddRef it. font.Open(gTrueTypeFont, kTrueTypeFontSize); font.SetTransform(12); // Layout some text. LineLayout lineLayout; LayoutSimple(L"Hello world", 11, 0, 0, &font, lineLayout); // Curve the text. Bezier2DIterator it; BaselineLayout baselineLayout; GlyphLayoutInfoEx gliArray[16]; it.MakeSemicircle(Point(0, 0), 100); baselineLayout.FollowBaseline(&lineLayout, gliArray, &it, 0.f, 0.f, kHALeft, kDirectionLTR); // Now you would take the lineLayout and gliArray and write it to your // vertex buffer system. The EATextRNA package has an example of this. } return 0; }
Example of how to do Unicode compression:
#include "Demo.h" #include <EAText/EATextCompression.h>
#include <EASTL/string.h>
int main(int, char**) { using namespace EA::Text; { // Demo SCSU compression eastl::string16 sOriginal(L"abcdefg xyzpdq four score and seven years ago save the cheerleader save the world"); eastl::string16 sCompressed; eastl::string16 sDecompressed; const uint32_t compressedSize = CompressSCSU(sOriginal.data(), (uint32_t)sOriginal.length(), NULL, 0); sCompressed.resize(compressedSize); CompressSCSU(sOriginal.data(), (uint32_t)sOriginal.length(), &sCompressed[0], sCompressed.length()); sDecompressed.resize(sOriginal.size()); DecompressSCSU(sCompressed.data(), (uint32_t)sCompressed.length(), &sDecompressed[0], (uint32_t)sDecompressed.length()); } { // Demo BOCU compression eastl::string16 sOriginal(L"abcdefg xyzpdq four score and seven years ago save the cheerleader save the world"); eastl::string16 sCompressed; eastl::string16 sDecompressed; const uint32_t compressedSize = CompressBOCU(sOriginal.data(), (uint32_t)sOriginal.length(), NULL, 0); sCompressed.resize(compressedSize); CompressBOCU(sOriginal.data(), (uint32_t)sOriginal.length(), &sCompressed[0], sCompressed.length()); sDecompressed.resize(sOriginal.size()); DecompressBOCU(sCompressed.data(), (uint32_t)sCompressed.length(), &sDecompressed[0], (uint32_t)sDecompressed.length()); } return 0; }