Difference between revisions of "XyzzyText"

From Second Life Wiki
Jump to navigation Jump to search
(copyedit)
m (language tags to <source>)
 
(39 intermediate revisions by 15 users not shown)
Line 1: Line 1:
XyzzyText is a new version of Xytext with some advanced features:
{{LSL Header}}
{{RightToc}}
 
Scripting tools to allow display of text on a prim: [[XyText 1.5]] , [[XyzzyText]], [[XyyyyzText]], [[XyText-UTF8]], [[XyzzyText-UTF8]], [[ZZText]], [[VariText]]
 
 
 
'''XyzzyText''' is a new version of [[XyText 1.5]] with some advanced features:


* Efficiency!  You can now control an entire linkset with just one script.
* Efficiency!  You can now control an entire linkset with just one script.
Line 6: Line 13:
* To use, pass the bank number to use in the "key" field of the link message of DISPLAY_STRING/EXTENDED.
* To use, pass the bank number to use in the "key" field of the link message of DISPLAY_STRING/EXTENDED.


=Copyright & Licensing=
<font size="1">
* Traven Sachs hereby releases his contributions to this under the MIT license (16 Char MESH Prim Mod).
* Gigs Taggart hereby releases his contributions to this under the MIT license.
* Gigs Taggart hereby releases his contributions to this under the MIT license.
* Thraxis Epsilon hereby releases his contributions to this under the MIT license.
* Strife Onizuka hereby releases his contributions to this under the MIT license.
* Huney Jewell hereby releases her contributions to this under the MIT license.
* Salahzar Stenvaag hereby releases his contributions to this under the MIT license. (photoshop/GIMP vbs script for alternative fonts plus [[ZZText]])
* Ruud Lathrop hereby releases her contributions to this under the MIT license.
* Joel Cloquet hereby releases his contributions to this under the MIT license.


</font>
=XyzzyText - 5 Character=
Example:
Example:
<pre>
<pre>
Line 32: Line 51:


Usage:
Usage:
<pre>
<source lang="lsl2">
integer DISPLAY_STRING      = 204000;  
integer DISPLAY_STRING      = 204000;  
integer DISPLAY_EXTENDED    = 204001;  
integer DISPLAY_EXTENDED    = 204001;  
Line 39: Line 58:
integer SET_FADE_OPTIONS    = 204004;  
integer SET_FADE_OPTIONS    = 204004;  
integer SET_FONT_TEXTURE    = 204005;  
integer SET_FONT_TEXTURE    = 204005;  
integer SET_LINE_COLOR      = 204006;
integer SET_COLOR          = 204007;  
integer SET_COLOR          = 204007;  
integer RESCAN_LINKSET      = 204008;
integer RESCAN_LINKSET      = 204008;
Line 66: Line 86:
}
}


</pre>
</source>


Main Script:
==Main Script==
<pre>
<source lang="lsl2">
////////////////////////////////////////////  
////////////////////////////////////////////  
// XyzzyText v2.1 Script (Set Line Color) by Huney Jewell
// XyzzyText v2.0 Script (5 Face, Single Texture)  
// XyzzyText v2.0 Script (5 Face, Single Texture)  
//
//
// Heavily Modified by Thraxis Epsilon and Gigs Taggart 5/2007
//
// Edited trivially by Joel Cloquet on 1/2011 to use the (relatively)
// new llSetLinkPrimitiveParamsFast function, thereby removing the need in
// some cases to use the slave script.
//
// Heavily Modified by Thraxis Epsilon, Gigs Taggart 5/2007 and Strife Onizuka 8/2007
// Rewrite to allow one-script-per-object operation w/ optional slaves
// Rewrite to allow one-script-per-object operation w/ optional slaves
// Enable prim-label functionality
// Enable prim-label functionality
Line 94: Line 120:
integer SET_FADE_OPTIONS    = 204004;  
integer SET_FADE_OPTIONS    = 204004;  
integer SET_FONT_TEXTURE    = 204005;  
integer SET_FONT_TEXTURE    = 204005;  
integer SET_LINE_COLOR      = 204006;
integer SET_COLOR          = 204007;  
integer SET_COLOR          = 204007;  
integer RESCAN_LINKSET      = 204008;
integer RESCAN_LINKSET      = 204008;
Line 121: Line 148:


// Used to hide the text after a fade-out.  
// Used to hide the text after a fade-out.  
key    TRANSPARENT    = "701917a8-d614-471f-13dd-5f4644e36e3c";  
key    TRANSPARENT    = "701917a8-d614-471f-13dd-5f4644e36e3c";
key    null_key        = NULL_KEY;
///////////// END CONSTANTS ////////////////  
///////////// END CONSTANTS ////////////////  


Line 169: Line 197:
   // <-0.74, 0, 0>  
   // <-0.74, 0, 0>  
      
      
   llSetLinkPrimitiveParams( link,[  
   llSetLinkPrimitiveParamsFast( link,[  
         PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.126, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,  
         PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.126, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,  
         PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,  
         PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,  
Line 203: Line 231:
   // Use these grid positions to display the correct textures/offsets.  
   // Use these grid positions to display the correct textures/offsets.  
   ShowChars(link,GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength.  
   ShowChars(link,GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength.  
     float Alpha;  
     float Alpha = 0.10;  
     for (Alpha = 0.10; Alpha <= 1.0; Alpha += 0.05)  
     for (; Alpha <= 1.0; Alpha += 0.05)  
       llSetLinkAlpha(link,Alpha, ALL_SIDES);  
       llSetLinkAlpha(link,Alpha, ALL_SIDES);  
           // See if we want to fade out as well.  
           // See if we want to fade out as well.  
Line 222: Line 250:
   // Look for escape sequences.  
   // Look for escape sequences.  
   integer length = 0;
   integer length = 0;
   list Parsed      = llParseString2List(str, [], [ESCAPE_SEQUENCE]);  
   list Parsed      = llParseString2List(str, [], (list)ESCAPE_SEQUENCE);  
   integer ParsedLen = llGetListLength(Parsed);  
   integer ParsedLen = llGetListLength(Parsed);  


Line 230: Line 258:
   integer IndicesLeft = 5;  
   integer IndicesLeft = 5;  


  integer i;
   string Token;  
   string Token;  
   integer Clipped;  
   integer Clipped;  
   integer LastWasEscapeSequence = FALSE;  
   integer LastWasEscapeSequence = FALSE;  
   // Work from left to right.  
   // Work from left to right.  
   for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {  
   integer i = 0;
  for (; i < ParsedLen && IndicesLeft > 0; ++i) {  
       Token = llList2String(Parsed, i);  
       Token = llList2String(Parsed, i);  


Line 248: Line 276:
           // Clip if necessary.  
           // Clip if necessary.  
           if (TokenLength > IndicesLeft) {  
           if (TokenLength > IndicesLeft) {  
               Token = llGetSubString(Token, 0, IndicesLeft - 1);
               TokenLength = llStringLength(Token = llGetSubString(Token, 0, IndicesLeft - 1));  
              TokenLength = llStringLength(Token);  
               IndicesLeft = 0;  
               IndicesLeft = 0;  
               Clipped = TRUE;  
               Clipped = TRUE;  
Line 259: Line 286:
           if (LastWasEscapeSequence) {  
           if (LastWasEscapeSequence) {  
               // Yes, the first character is an escape character, the rest are normal.  
               // Yes, the first character is an escape character, the rest are normal.  
               length += 3;
               length += 2 + TokenLength;
              // This is the extended character.
              if(render)             
                    Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
 
              // These are the normal characters.
              length+=TokenLength - 1;  
               if(render)
               if(render)
               {
               {
                  integer j;  
                    // This is the extended character.
                  for (j = 1; j < TokenLength; j++)  
                    Indices += (llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95);
                  {
                   
                       Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];  
                    // These are the normal characters.
                    integer j = 1;  
                    for (; j < TokenLength; ++j)  
                    {
                       Indices += llSubStringIndex(gCharIndex, llGetSubString(Token, j, j));  
                     }
                     }
                 }
                 }
Line 277: Line 302:
           else { // Normal string.  
           else { // Normal string.  
               // Just add the characters normally.  
               // Just add the characters normally.  
               length+=TokenLength;
               length += TokenLength;
               if(render)
               if(render)
               {               
               {               
                   integer j;  
                   integer j = 0;  
                   for (j = 0; j < TokenLength; j++)  
                   for (; j < TokenLength; ++j)  
                   {                   
                   {                   
                       Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
                       Indices += llSubStringIndex(gCharIndex, llGetSubString(Token, j, j));
                   }
                   }
                 }
                 }
Line 327: Line 352:
PassToRender(integer render,string message, integer bank)
PassToRender(integer render,string message, integer bank)
{
{
    float time;
//    float time;
     integer extendedlen = 0;
     integer extendedlen = 0;
     integer link;
     integer link;
    integer x;
     integer i = 0;
     integer i = 0;
     integer msgLen = llStringLength(message);
     integer msgLen = llStringLength(message);
Line 343: Line 367:
     integer bank_highest_dirty=llList2Integer(gBankingData, (bank * BANK_STRIDE) + 2);
     integer bank_highest_dirty=llList2Integer(gBankingData, (bank * BANK_STRIDE) + 2);
      
      
     for (x=0;x < msgLen;x = x + 5)
     integer x = 0;
    for (;x < msgLen;x = x + 5)
     {
     {


Line 349: Line 374:
         {
         {
             //set the dirty to max, and bail out, we're done
             //set the dirty to max, and bail out, we're done
             gBankingData=llListReplaceList(gBankingData, [bank_length], (bank * BANK_STRIDE) + 2, (bank * BANK_STRIDE) + 2);
             gBankingData=llListReplaceList(gBankingData, (list)bank_length, (bank * BANK_STRIDE) + 2, (bank * BANK_STRIDE) + 2);
             return;
             return;
         }  
         }
          
          
         link = unpack(gXyTextPrims,(i + bank_offset));
         link = unpack(gXyTextPrims,(i + bank_offset));
         TextToRender = llGetSubString(message, x, x + 15);
         TextToRender = llGetSubString(message, x, x + 15);
          
          
         if(gSlaveRegistered && (link % (num_slaves +1) != 0))
         if(gSlaveRegistered && (link % (num_slaves + 1)))
         {
         {
             slave_name=llList2String(gSlaveNames, (link % (num_slaves + 1)) - 1);
             slave_name=llList2String(gSlaveNames, (link % (num_slaves + 1)) - 1);
Line 393: Line 418:
                 RenderWithEffects(link,TextToRender);
                 RenderWithEffects(link,TextToRender);
         }
         }
         i=i+1;             
         ++i;             
     }
     }
      
      
Line 414: Line 439:
             RenderString(link,"    ");
             RenderString(link,"    ");
         }
         }
         i=i+1;         
         ++i;         
     }
     }
     gBankingData=llListReplaceList(gBankingData, [current_highest_dirty], (bank * BANK_STRIDE) + 2, (bank * BANK_STRIDE) + 2);
     gBankingData=llListReplaceList(gBankingData, (list)current_highest_dirty, (bank * BANK_STRIDE) + 2, (bank * BANK_STRIDE) + 2);
}
}


// Bitwise Voodoo by Gigs Taggart
// Bitwise Voodoo by Gigs Taggart and optimized by Strife Onizuka
list gXyTextPrims;
list gXyTextPrims;


Line 426: Line 451:
{//ignores avatars.
{//ignores avatars.
     integer a = llGetNumberOfPrims();
     integer a = llGetNumberOfPrims();
     if(1 < a)
     //Mono tweak
        while(llGetAgentSize(llGetLinkKey(a)))
    vector size = llGetAgentSize(llGetLinkKey(a));
            --a;
    while(size.z > 0)
    {
        --a;
        size = llGetAgentSize(llGetLinkKey(a));
    }
     return a;
     return a;
}
}


//functions to pack 8-bit shorts into ints
//functions to pack 8-bit shorts into ints
list pack_and_insert(list in_list, integer pos, integer value)
list pack_and_insert(list in_list, integer pos, integer value)
{
{
    //figure out the bitpack position
//    //figure out the bitpack position
    integer pack=pos%4; //4 bytes per int
//    integer pack = pos & 3; //4 bytes per int
    pos=pos/4;
//    pos=pos >> 2;
    integer shifted=value << (pack * 8);
//    integer shifted = value << (pack << 3);
    integer old_value=llList2Integer(in_list, pos);
//    integer old_value = llList2Integer(in_list, pos);
    shifted=old_value | shifted;
//    shifted = old_value | shifted;
    in_list = llListReplaceList(in_list, [ shifted ], pos, pos);
//    in_list = llListReplaceList(in_list, (list)shifted, pos, pos);
     return in_list;
//    return in_list;
    //Safe optimized version
    integer index = pos >> 2;
     return llListReplaceList(in_list, (list)(llList2Integer(in_list, index) | (value << ((pos & 3) << 3))), index, index);
}
}


integer unpack(list in_list, integer pos)
integer unpack(list in_list, integer pos)
{
{
     integer pack=pos%4;
     return (llList2Integer(in_list, pos >> 2) >> ((pos & 3) << 3)) & 0x000000FF;//unsigned
    pos=pos/4;
//    return (llList2Integer(in_list, pos >> 2) << (((~pos) & 3) << 3)) >> 24;//signed
    integer value=llList2Integer(in_list, pos);
    return value >> (pack * 8) & 0x000000FF;
}
}


change_color(vector color)
change_color(vector color)
{
{
     integer num_prims=llGetListLength(gXyTextPrims);
     integer num_prims=llGetListLength(gXyTextPrims) << 2;
    num_prims=num_prims*4;
      
      
     integer i;
     integer i = 0;
      
      
     for (i=0;i<=num_prims;i++)
     for (; i<=num_prims; ++i)
     {
     {
         integer link = unpack(gXyTextPrims,i);
         integer link = unpack(gXyTextPrims,i);
         if (link==0)
         if (!link)
             return;
             return;
           
       
           
         llSetLinkPrimitiveParamsFast( link,[  
         llSetLinkPrimitiveParams( link,[  
            PRIM_COLOR, FACE_1, color, 1.0,
        PRIM_COLOR, FACE_1, color, 1.0,
            PRIM_COLOR, FACE_2, color, 1.0,
        PRIM_COLOR, FACE_2, color, 1.0,
            PRIM_COLOR, FACE_3, color, 1.0,
        PRIM_COLOR, FACE_3, color, 1.0,
            PRIM_COLOR, FACE_4, color, 1.0,
        PRIM_COLOR, FACE_4, color, 1.0,
            PRIM_COLOR, FACE_5, color, 1.0
        PRIM_COLOR, FACE_5, color, 1.0
         ]);
         ]);  
     }
     }
}
}


change_line_color(integer bank, vector color)
{   
    //get the bank offset and length
    integer i = llList2Integer(gBankingData, (bank * BANK_STRIDE));
    integer bank_end = i + llList2Integer(gBankingData, (bank * BANK_STRIDE) + 1);
    for (; i < bank_end; ++i)
    {   
        integer link = unpack(gXyTextPrims,i);
        if (!link)
            return;
       
        llSetLinkPrimitiveParamsFast( link,[
            PRIM_COLOR, FACE_1, color, 1.0,
            PRIM_COLOR, FACE_2, color, 1.0,
            PRIM_COLOR, FACE_3, color, 1.0,
            PRIM_COLOR, FACE_4, color, 1.0,
            PRIM_COLOR, FACE_5, color, 1.0
        ]);
    }
}




Line 483: Line 532:
{
{
     integer num_prims=get_number_of_prims();
     integer num_prims=get_number_of_prims();
    integer x;
     string link_name;
     string link_name;
     integer bank=0;
     integer bank=0;
    integer bank_empty=FALSE;
     integer prims_pointer=0; //"pointer" to the next entry to be used in the gXyTextPrims list.
     integer prims_pointer=0; //"pointer" to the next entry to be used in the gXyTextPrims list.
      
      
     list temp_bank;
     list temp_bank = [];
     integer temp_bank_stride=2;
     integer temp_bank_stride=2;
   
      
      
     //FIXME: font texture might should be per-bank
     //FIXME: font texture might should be per-bank
     llMessageLinked(LINK_THIS, SET_FONT_TEXTURE, "" ,gFontTexture);
     llMessageLinked(LINK_THIS, SET_FONT_TEXTURE, "", gFontTexture);
      
      
    // moving this before the prim scan so that the slaves properly configure themseves before
    // any requests to display
    llMessageLinked(LINK_THIS, SLAVE_RESET, "" , null_key);
     gXyTextPrims=[];
     gXyTextPrims=[];
     for (x=0;x<64;x++)
     integer x=0;
    for (;x<64;++x)
     {
     {
         gXyTextPrims= (gXyTextPrims = []) + gXyTextPrims + [0];  //we need to pad out the list to make it easier to add things in any order later
         gXyTextPrims = (gXyTextPrims = []) + gXyTextPrims + 0;  //we need to pad out the list to make it easier to add things in any order later
     }
     }
      
      
      
     gBankingData = [];
     while(!bank_empty)
     @loop;
     {
     {
        bank_empty=TRUE;
       
         //loop over all prims, looking for ones in the current bank
         //loop over all prims, looking for ones in the current bank
         for(x=0;x<=num_prims;x++)
         for(x=0;x<=num_prims;++x)
         {
         {
             link_name=llGetLinkName(x);
             link_name=llGetLinkName(x);
             list tmp = llParseString2List(link_name, ["-"], []);
             list tmp = llParseString2List(link_name, (list)"-", []);
             string xyzzytext = llList2String(tmp,0);
             if(llList2String(tmp,0) == "xyzzytext")
            if(xyzzytext == "xyzzytext")
             {
             {
                 integer prims_bank=llList2Integer(tmp,1);
                 if (llList2Integer(tmp,1) == bank)
                if (prims_bank==bank)
                 {
                 {
                     bank_empty=FALSE;
                     temp_bank += llList2Integer(tmp,2) + (list)x;
                    integer pos=llList2Integer(tmp,2);
                    temp_bank+=[pos];
                    temp_bank+=[x];
                 }
                 }
             }
             }
Line 528: Line 571:
         }
         }


         if (!bank_empty)
         if (temp_bank != [])
         {
         {
             //sort the current bank
             //sort the current bank
             temp_bank=llListSort(temp_bank, temp_bank_stride, TRUE);
             temp_bank = llListSort(temp_bank, temp_bank_stride, TRUE);
              
              
            integer y;
             integer temp_len = llGetListLength(temp_bank);
             integer temp_len=llGetListLength(temp_bank);
              
              
             //store metadata
             //store metadata
             gBankingData+=[prims_pointer];
             gBankingData += [prims_pointer, temp_len/temp_bank_stride, 0];
            gBankingData+=[temp_len/temp_bank_stride];
            gBankingData+=[0];
              
              
             //repack the bank into the prim list
             //repack the bank into the prim list
             for (y=0; y < temp_len; y+=temp_bank_stride)
             for (x = 0; x < temp_len; x += temp_bank_stride)
             {
             {
                 gXyTextPrims = pack_and_insert(gXyTextPrims, prims_pointer, llList2Integer(temp_bank, y+1));
                 gXyTextPrims = pack_and_insert(gXyTextPrims, prims_pointer, llList2Integer(temp_bank, x + 1));
                 prims_pointer++;
                 ++prims_pointer;
             }
             }
            ++bank;
            temp_bank=[];
            jump loop;
         }
         }
       
        temp_bank=[];       
        bank++;
     }
     }
      
      
    llMessageLinked(LINK_THIS, SLAVE_RESET, "" , NULL_KEY);
     //llOwnerSay((string)llGetFreeMemory());
     //llOwnerSay((string)llGetFreeMemory());
}
}
Line 571: Line 610:
    
    
   link_message(integer sender, integer channel, string data, key id) {  
   link_message(integer sender, integer channel, string data, key id) {  
         if (id==NULL_KEY)
         if (id==null_key)
             id="0";
             id="0";
              
              
         if (channel == DISPLAY_STRING) {  
         if (channel == DISPLAY_STRING) {  
          PassToRender(1,data, (integer)((string)id));
            PassToRender(1,data, (integer)((string)id));  
          return;  
         }  
         }  
         if (channel == DISPLAY_EXTENDED) {  
         else if (channel == DISPLAY_EXTENDED) {  
          PassToRender(2,data, (integer)((string)id));
            PassToRender(2,data, (integer)((string)id));  
          return;  
         }  
         }  
         if (channel == REMAP_INDICES) {  
         else if (channel == REMAP_INDICES) {  
          // Parse the message, splitting it up into index values.  
            // Parse the message, splitting it up into index values.  
          list Parsed = llCSV2List(data);  
            list Parsed = llCSV2List(data);  
          integer i;  
            integer i = 0;  
          // Go through the list and swap each pair of indices.  
            // Go through the list and swap each pair of indices.  
          for (i = 0; i < llGetListLength(Parsed); i += 2) {  
            for (; i < llGetListLength(Parsed); i += 2) {  
              integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );  
                integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );  
              integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );  
                integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );  
          
          
              // Swap these index values.  
                // Swap these index values.  
              string Value1 = llGetSubString(gCharIndex, Index1, Index1);  
                string Value1 = llGetSubString(gCharIndex, Index1, Index1);  
              string Value2 = llGetSubString(gCharIndex, Index2, Index2);  
                string Value2 = llGetSubString(gCharIndex, Index2, Index2);  
          
          
              gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);  
                gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);  
              gCharIndex = llInsertString(gCharIndex, Index1, Value2);  
                gCharIndex = llInsertString(gCharIndex, Index1, Value2);
          
          
              gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);  
                gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
              gCharIndex = llInsertString(gCharIndex, Index2, Value1);  
                gCharIndex = llInsertString(gCharIndex, Index2, Value1);
          }  
            }  
          return;
         }  
         }  
          
         else if (channel == RESCAN_LINKSET)
        if (channel == RESCAN_LINKSET)
         {
         {
             init();
             init();
         }
         }
          
         else if (channel == RESET_INDICES) {  
        if (channel == RESET_INDICES) {  
            // Restore the character index back to default settings.  
          // Restore the character index back to default settings.  
            ResetCharIndex();  
          ResetCharIndex();
          return;  
         }  
         }  
         if (channel == SET_FADE_OPTIONS) {  
         else if (channel == SET_FADE_OPTIONS) {  
          // Change the channel we listen to for cell commands, the  
            // Change the channel we listen to for cell commands, the  
          // starting character position to extract from, and  
            // starting character position to extract from, and  
          // special effect attributes.  
            // special effect attributes.  
          list Parsed = llCSV2List(data);  
            list Parsed = llCSV2List(data);  
          gCellUseFading      = (integer) llList2String(Parsed, 0);  
            gCellUseFading      = (integer) llList2String(Parsed, 0);  
          gCellHoldDelay      = (float)  llList2String(Parsed, 1);
            gCellHoldDelay      = (float)  llList2String(Parsed, 1);
          return;  
         }  
         }  
         if (channel == SET_FONT_TEXTURE) {  
         else if (channel == SET_FONT_TEXTURE) {  
          // Use the new texture instead of the current one.  
            // Use the new texture instead of the current one.  
          gFontTexture = id;
            gFontTexture = id;  
          return;  
         }  
         }  
         if (channel == SET_COLOR) {  
         else if (channel == SET_COLOR) {
          vector newColor = (vector)data;  
            change_color((vector)data);  
          change_color(newColor);  
        }
        else if (channel == SET_LINE_COLOR) {
            change_line_color((integer)((string)id), (vector)data);  
         }  
         }  
     
         else if (channel == REGISTER_SLAVE)
         if (channel == REGISTER_SLAVE)
         {
         {
             if(~llListFindList(gSlaveNames, [data]))
             if(!~llListFindList(gSlaveNames, (list)data))
             {//it already exists
             {//isn't registered yet
                 llMessageLinked(LINK_THIS, SLAVE_RECOGNIZED, data , NULL_KEY);
                 gSlaveNames += data;
                 //llOwnerSay((string)llGetListLength(gSlaveNames) + " Slave, Existing Slave Recognized: " + data);
                gSlaveRegistered=TRUE;
                return;
                 //llOwnerSay((string)llGetListLength(gSlaveNames) + " Slave(s) Recognized: " + data);
             }
             }
           
//            else
            gSlaveNames+=[data];
//            {//it already exists
            llMessageLinked(LINK_THIS, SLAVE_RECOGNIZED, data , NULL_KEY);
//               llOwnerSay((string)llGetListLength(gSlaveNames) + " Slave, Existing Slave Recognized: " + data);
            gSlaveRegistered=TRUE;
//            }
            //llOwnerSay((string)llGetListLength(gSlaveNames) + " Slave(s) Recognized: " + data);
            llMessageLinked(LINK_THIS, SLAVE_RECOGNIZED, data , null_key);
         }
         }
  }  
    }  
    
    
    
    
Line 654: Line 686:
         if(change&CHANGED_INVENTORY)
         if(change&CHANGED_INVENTORY)
         {
         {
             if(!gSlaveRegistered)         
             if(gSlaveRegistered)         
                return;
       
         
            integer num_slaves=llGetListLength(gSlaveNames);
 
            integer x;
            for (x=0;x<num_slaves;x++)
             {
             {
                 if ((llGetInventoryType(llList2String(gSlaveNames, x)) == -1)&&(x<llGetListLength(gSlaveNames)))
                 //by using negative indexes they don't need to be adjusted when an entry is deleted.
                integer x = ~llGetListLength(gSlaveNames);
                while(++x)
                 {
                 {
                     //llOwnerSay("Slave Removed: " + llList2String(gSlaveNames, x));
                     if (!~llGetInventoryType(llList2String(gSlaveNames, x)))
                    gSlaveNames = llDeleteSubList(gSlaveNames, x, x);
                    {
                     x--;//our indices shifted
                        //llOwnerSay("Slave Removed: " + llList2String(gSlaveNames, x));
                        gSlaveNames = llDeleteSubList(gSlaveNames, x, x);
                     }
                 }
                 }
                gSlaveRegistered = !(gSlaveNames == []);
             }
             }
         }
         }
Line 674: Line 704:
}  
}  


</pre>
</source>


 
==Slave Script==
Slave Script:
<source lang="lsl2">
<pre>
////////////////////////////////////////////  
////////////////////////////////////////////  
// XyText v2.0 SLAVE Script (5 Face, Single Texture)  
// XyText v2.0 SLAVE Script (5 Face, Single Texture)  
Line 754: Line 783:
   // <-0.74, 0, 0>  
   // <-0.74, 0, 0>  
      
      
   llSetLinkPrimitiveParams( link,[  
   llSetLinkPrimitiveParamsFast( link,[  
         PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.126, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,  
         PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.126, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0,  
         PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,  
         PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0,  
Line 785: Line 814:
   integer IndicesLeft = 5;  
   integer IndicesLeft = 5;  


   integer i;  
   integer i = 0;  
   string Token;  
   string Token;  
   integer Clipped;  
   integer Clipped;  
   integer LastWasEscapeSequence = FALSE;  
   integer LastWasEscapeSequence = FALSE;  
   // Work from left to right.  
   // Work from left to right.  
   for (i = 0; i < ParsedLen && IndicesLeft > 0; i++) {  
   for (; i < ParsedLen && IndicesLeft > 0; ++i) {  
       Token = llList2String(Parsed, i);  
       Token = llList2String(Parsed, i);  


Line 816: Line 845:


               // This is the extended character.  
               // This is the extended character.  
               Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];  
               Indices += (llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95);  


               // These are the normal characters.  
               // These are the normal characters.  
               integer j;  
               integer j = 1;  
               for (j = 1; j < TokenLength; j++)  
               for (; j < TokenLength; ++j)  
                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];  
                   Indices += llSubStringIndex(gCharIndex, llGetSubString(Token, j, j));  
           }  
           }  
           else { // Normal string.  
           else { // Normal string.  
               // Just add the characters normally.  
               // Just add the characters normally.  
               integer j;  
               integer j;  
               for (j = 0; j < TokenLength; j++)  
               for (j = 0; j < TokenLength; ++j)  
                   Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];  
                   Indices += llSubStringIndex(gCharIndex, llGetSubString(Token, j, j));  
           }  
           }  


Line 962: Line 991:




</pre>
</source>
 


Prim Setup (Caution!  Not compatible with 5 face double texture Xytext):
==Prim Setup==
<pre>
'''(Caution!  Not compatible with 5 face double texture Xytext)'''
<source lang="lsl2">
////////////////////////////////////////////
////////////////////////////////////////////
// XyzzyText Prim Setup Script (5 Face)
// XyzzyText Prim Setup Script (5 Face)
Line 991: Line 1,020:
     }  
     }  
}  
}  
</pre>
</source>
 
 
= How to produce textures with alternative fonts for 5 character version (Photoshop VBscript version) =
 
Note: If you need to use XyzzyText for European or UTF-8 other languages, please see the following link: [[XyzzyText-UTF8]]
Use the following section for GIMP script-fu if you prefer opensource alternative. Use the following link: [[ZZText]] for an alternative international solution (still based on xytext and xyzzytext) using even fewer textures and lag than the xytext solution.
 
If you need to display with another font, italics or other characters, you can do the following :)
 
Using Photoshop VB Scripting on windows, just open up Photoshop CS3, and create with notepad the following MakeFonts.vbs:
 
<source lang="vb">
Dim appRef
Set appRef = CreateObject( "Photoshop.Application" )
' Remember current unit settings and then set units to
' the value expected by this script
Dim originalRulerUnits
originalRulerUnits = appRef.Preferences.RulerUnits
appRef.Preferences.RulerUnits = 1 ' psPixels
' Create a new 512*512 pixel document and assign it to a variable.
Dim docRef
Dim artLayerRef
Dim textItemRef
dim rows(10)
rows(1)=" !""#$%&'()"
rows(2)="*+,-./0123"
rows(3)="456789:;<="
rows(4)=">?@ABCDEFG"
rows(5)="HIJKLMNOPQ"
rows(6)="RSTUVWXYZ["
rows(7)="\]^_`abcde"
rows(8)="fghijklmno"
rows(9)="pqrstuvwxy"
rows(10)="z{|}~·ΘΩ√"     
 
dim fontname
fontname="Courier"
 
Set docRef = appRef.Documents.Add(512, 512, 72, "Table for font " & fontname, 2, 3, 1)
' Create a new art layer containing text
Dim colorObj
Set colorObj=CreateObject("Photoshop.SolidColor")
colorObj.RGB.Red=255
colorObj.RGB.Green=255
colorObj.RGB.Blue=255
' Set the contents of the text layer.
for i=1 to 10
  for j=1 to 10
    Set artLayerRef = docRef.ArtLayers.Add
    artLayerRef.Kind = 2
    Set textItemRef = artLayerRef.TextItem
    ' textItemRef.Font = fontname
    textItemRef.Color=colorObj
    textItemRef.Justification = 2
    textItemRef.Font = "A Charming Font"
    textItemRef.Size = 38
    textItemRef.Contents = mid(rows(i),j,1)
    textItemRef.Position = Array((j-1)*51.2+25,i*51.2-7)
  next
next
' Restore unit settingPhotoshop CS3
appRef.Preferences.RulerUnits = originalRulerUnits
</source>
 
Just click on the file in internet explorer and it will start working with your photoshop (I'll take a bit to display all the 100 characters, maybe around 1 sec for character).
 
This will layout the 100 characters in photoshop on 100 different layers. Then do the following:
 
* select all the layers with text and go to Text tool and change to whatever font (size etc) you want
* maybe need to add a black layer to see your fonts :) (after you have to hid or delete this)
* merge all the layers in just one
* add an alpha channel for transparency (all black)
* select all in the merged text layer and copy it to alpha channel
* enable the alpha channel and the RGB channel so that you see your font set and the transparent red
* save the font as TGA preserving alpha channel
* upload it to SecondLife 10 L$
* get the UUID from the inventory and specify it instead of the standard template.
 
= How to produce textures with alternative fonts for 5 character version (GIMP script-fu version) =
 
Use the following script for generating characters with GIMP:
 
To use it you must put under the $HOME/.gimpXXX/scripts directory and name it something.scm.
Then you will find it under gimp <Toolbox>/Xtns/Script-Fu/Text. Clicking on it you are prompted for font to use (use monospaced fonts Andale Mono for instance). (Change only font and fontsize 50 should be ok, others are hard-coded in the script). Adapt with new unicode characters if you need them.
 
Note: If you need to use XyzzyText for European or UTF-8 other languages, please see the following link: [[XyzzyText-UTF8]]
Use the following section for GIMP script-fu if you prefer opensource alternative. Use the following link: [[ZZText]] for an alternative international solution (still based on xytext and xyzzytext) using even fewer textures and lag than the xytext solution.
 
 
<source lang="scheme">
(define (script-fu-xytext-simple inText inFont inFontSize inTextColor)
  (let*
      (
      ; define our local variables
      ; create a new image:
      (theImageWidth  512)
      (theImageHeight 512)
      (theImage)
      (theLayer )
      (x 0)
      (y 0)
      (p 32)
      (theText)
      (rows #(
              " !\"#$%&'()"
              "*+,-./0123"
              "456789:;<="
              ">?@ABCDEFG"
              "HIJKLMNOPQ"
              "RSTUVWXYZ["
              "\\]^_`abcde"
              "fghijklmno"
              "pqrstuvwxy"
 
              ;"z{|}~·thetaOmegaRadix.o "
              (list #\z #\{ #\| #\} #\~ #x387 #x3F4 #x3A9r #x221A #x387 )
              )
      )
      (el 0)
      (ch 0)
      ) ;end of our local variables
 
  ; builds image
  (set! theImage (car    (gimp-image-new theImageWidth theImageHeight RGB)))
  ; builds layer
  (set! theLayer (car (gimp-layer-new theImage theImageWidth theImageHeight RGB-IMAGE "layer 1" 100 NORMAL) ) )
  ; add layer to image
  (gimp-image-add-layer theImage theLayer 0)
 
  ; add alpha and set foreground
  (gimp-layer-add-alpha theLayer)
  (gimp-palette-set-foreground inTextColor)
 
  ; clear all the image and remove selection
  (gimp-selection-all theImage)
  (gimp-edit-clear theLayer)
  (gimp-selection-none theImage)
 
  ; do actual plot of characters
  (while (< x 10)
    (while (< y 10)
      (set! el (vector-ref rows x))
      (set! ch (if (string? el) (substring el y (+ y 1)) (list-ref el (+ y 1))))
      (if (integer? ch) (set! ch (integer->char ch)))
      (if (char? ch)(set! ch (string ch)))
      (set! p ch)
      (set! theText (car (gimp-text-fontname theImage theLayer
          (+ (* y 51.2) 12) (- (* x 51.2) 7)
          p    0    TRUE
          inFontSize PIXELS
          inFont)))
 
        ; go on next char
        ;(set! p (+ p 1))
        (set! y (+ y 1))
      )
 
    (set! y 0)
    (set! x (+ x 1))
  )
 
  ; apply the layers (apply transparence
  (gimp-image-merge-visible-layers theImage 0)
 
  (gimp-display-new theImage)
  )
)
 
(script-fu-register
  "script-fu-xytext-simple"          ;func name
  "XYText-simple"                    ;menu label
  "Generate font for xytext-simple"  ;description
  "Salahzar Stenvaag"                ;author
  "copyright 2008, Salahzar Stenvaag" ;copyright notice
  "October 27, 1997"                  ;date created
  ""                                  ;image type that the script works on
  SF-STRING      "Text:"        "Text Box"          ;a string variable
  SF-FONT        "Font:"        "Andale Mono"        ;a fixed font variable
  SF-ADJUSTMENT  "Font size"    '(50 1 1000 1 10 0 1);a spin-button
  SF-COLOR      "Color:"        '(255 255 255)      ;color variable
)
 
(script-fu-menu-register "script-fu-xytext-simple" "<Toolbox>/Xtns/Text")
</source>
 
= XyzzyText - 10 Character =
 
== Master Script ==
<source lang="lsl2">
////////////////////////////////////////////
// XyzzyText v2.1(10-Char) by Thraxis Epsilon
// XyzzyText v2.1 Script (Set Line Color) by Huney Jewell
// XyzzyText v2.0 Script (5 Face, Single Texture)
//
// Edited trivially by Joel Cloquet on 1/2011 to use the (relatively)
// new llSetLinkPrimitiveParamsFast function, thereby removing the need in
// some cases to use the slave script.
//
// Heavily Modified by Thraxis Epsilon, Gigs Taggart 5/2007 and Strife Onizuka 8/2007
// Rewrite to allow one-script-per-object operation w/ optional slaves
// Enable prim-label functionality
// Enabled Banking
// Enabled 10-char per prim
//
// Modified by Kermitt Quirk 19/01/2006
// To add support for 5 face prim instead of 3
//
// Core XyText Originally Written by Xylor Baysklef
//
//
////////////////////////////////////////////
 
/////////////// CONSTANTS ///////////////////
// XyText Message Map.
integer DISPLAY_STRING      = 204000;
integer DISPLAY_EXTENDED    = 204001;
integer REMAP_INDICES      = 204002;
integer RESET_INDICES      = 204003;
integer SET_FADE_OPTIONS    = 204004;
integer SET_FONT_TEXTURE    = 204005;
integer SET_LINE_COLOR      = 204006;
integer SET_COLOR          = 204007;
integer RESCAN_LINKSET      = 204008;
 
//internal API
integer REGISTER_SLAVE      = 205000;
integer SLAVE_RECOGNIZED    = 205001;
integer SLAVE_DISPLAY      = 205003;
integer SLAVE_DISPLAY_EXTENDED = 205004;
integer SLAVE_RESET        = 205005;
 
// This is an extended character escape sequence.
string  ESCAPE_SEQUENCE = "\\e";
 
// This is used to get an index for the extended character.
string  EXTENDED_INDEX  = "123456789abcdef";
 
 
// Face numbers.
integer FACE_1          = 3;
integer FACE_2          = 7;
integer FACE_3          = 4;
integer FACE_4          = 6;
integer FACE_5          = 1;
 
// Used to hide the text after a fade-out.
key    TRANSPARENT    = "701917a8-d614-471f-13dd-5f4644e36e3c";
key    null_key        = NULL_KEY;
 
// This is a list of textures for all 2-character combinations.
list    CHARACTER_GRID  = [
        "00e9f9f7-0669-181c-c192-7f8e67678c8d",
        "347a5cb6-0031-7ec0-2fcf-f298eebf3c0e",
        "4e7e689e-37f1-9eca-8596-a958bbd23963",
        "19ea9c21-67ba-8f6f-99db-573b1b877eb1",
        "dde7b412-cda1-652f-6fc2-73f4641f96e1",
        "af6fa3bb-3a6c-9c4f-4bf5-d1c126c830da",
        "a201d3a2-364b-43b6-8686-5881c0f82a94",
        "b674dec8-fead-99e5-c28d-2db8e4c51540",
        "366e05f3-be6b-e5cf-c33b-731dff649caa",
        "75c4925c-0427-dc0c-c71c-e28674ff4d27",
        "dcbe166b-6a97-efb2-fc8e-e5bc6a8b1be6",
        "0dca2feb-fc66-a762-db85-89026a4ecd68",
        "a0fca76f-503a-946b-9336-0a918e886f7a",
        "67fb375d-89a1-5a4f-8c7a-0cd1c066ffc4",
        "300470b2-da34-5470-074c-1b8464ca050c",
        "d1f8e91c-ce2b-d85e-2120-930d3b630946",
        "2a190e44-7b29-dadb-0bff-c31adaf5a170",
        "75d55e71-f6f8-9835-e746-a45f189f30a1",
        "300fac33-2b30-3da3-26bc-e2d70428ec19",
        "0747c776-011a-53ce-13ee-8b5bb9e87c1e",
        "85a855c3-a94f-01ca-33e0-7dde92e727e2",
        "cbc1dab2-2d61-2986-1949-7a5235c954e1",
        "f7aef047-f266-9596-16df-641010edd8e1",
        "4c34ebf7-e5e1-2e1a-579f-e224d9d5e71b",
        "4a69e98c-26a5-ad05-e92e-b5b906ad9ef9",
        "462a9226-2a97-91ac-2d89-57ab33334b78",
        "20b24b3a-8c57-82ee-c6ed-555003f5dbcd",
        "9b481daa-9ea8-a9fa-1ee4-ab9a0d38e217",
        "c231dbdc-c842-15b0-7aa6-6da14745cfdc",
        "c97e3cbb-c9a3-45df-a0ae-955c1f4bf9cf",
        "f1e7d030-ff80-a242-cb69-f6951d4eae3b",
        "ed32d6c4-d733-c0f1-f242-6df1d222220d",
        "88f96a30-dccf-9b20-31ef-da0dfeb23c72",
        "252f2595-58b8-4bcc-6515-fa274d0cfb65",
        "f2838c4f-de80-cced-dff8-195dfdf36b2c",
        "cc2594fe-add2-a3df-cdb3-a61711badf53",
        "e0ce2972-da00-955c-129e-3289b3676776",
        "3e0d336d-321f-ddfa-5c1b-e26131766f6a",
        "d43b1dc4-6b51-76a7-8b90-38865b82bf06",
        "06d16cbb-1868-fd1d-5c93-eae42164a37d",
        "dd5d98cf-273e-3fd0-f030-48be58ee3a0b",
        "0e47c89e-de4a-6233-a2da-cb852aad1b00",
        "fb9c4a55-0e13-495b-25c4-f0b459dc06de",
        "e3ce8def-312c-735b-0e48-018b6799c883",
        "2f713216-4e71-d123-03ed-9c8554710c6b",
        "4a417d8a-1f4f-404b-9783-6672f8527911",
        "ca5e21ec-5b20-5909-4c31-3f90d7316b33",
        "06a4fcc3-e1c4-296d-8817-01f88fbd7367",
        "130ac084-6f3c-95de-b5b6-d25c80703474",
        "59d540a0-ae9d-3606-5ae0-4f2842b64cfa",
        "8612ae9a-f53c-5bf4-2899-8174d7abc4fd",
        "12467401-e979-2c49-34e0-6ac761542797",
        "d53c3eaa-0404-3860-0675-3e375596c3e3",
        "9f5b26bd-81d3-b25e-62fe-5b671d1e3e79",
        "f57f0b64-a050-d617-ee00-c8e9e3adc9cb",
        "beff166a-f5f3-f05e-e020-98f2b00e27ed",
        "02278a65-94ba-6d5e-0d2b-93f2e4f4bf70",
        "a707197d-449e-5b58-846c-0c850c61f9d6",
        "021d4b1a-9503-a44f-ee2b-976eb5d80e68",
        "0ae2ffae-7265-524d-cb76-c2b691992706"];
list    CHARACTER_GRID2  = [       
        "f6e41cf2-1104-bd0b-0190-dffad1bac813",
        "2b4bb15e-956d-56ae-69f5-d26a20de0ce7",
        "f816da2c-51f1-612a-2029-a542db7db882",
        "345fea05-c7be-465c-409f-9dcb3bd2aa07",
        "b3017e02-c063-5185-acd5-1ef5f9d79b89",
        "4dcff365-1971-3c2b-d73c-77e1dc54242a"
          ];
 
///////////// END CONSTANTS ////////////////
 
///////////// GLOBAL VARIABLES ///////////////
// All displayable characters.  Default to ASCII order.
string gCharIndex;
// This is the channel to listen on while acting
// as a cell in a larger display.
integer gCellChannel      = -1;
// This is the starting character position in the cell channel message
// to render.
integer gCellCharPosition = 0;
// This is whether or not to use the fade in/out special effect.
integer gCellUseFading      = FALSE;
// This is how long to display the text before fading out (if using
// fading special effect).
// Note: < 0  means don't fade out.
float  gCellHoldDelay      = 1.0;
 
integer gSlaveRegistered;
list gSlaveNames;
 
integer BANK_STRIDE=3; //offset, length, highest_dirty
list gBankingData;
 
/////////// END GLOBAL VARIABLES ////////////
 
ResetCharIndex() {
    gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
    // \" <-- Fixes LSL syntax highlighting bug.
    gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
    gCharIndex += "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n";
}
 
vector GetGridPos(integer index1, integer index2) {
    // There are two ways to use the lookup table...
    integer Col;
    integer Row;
    if (index1 >= index2) {
        // In this case, the row is the index of the first character:
        Row = index1;
        // And the col is the index of the second character (x2)
        Col = index2 * 2;
    }
    else { // Index1 < Index2
        // In this case, the row is the index of the second character:
        Row = index2;
        // And the col is the index of the first character, x2, offset by 1.
        Col = index1 * 2 + 1;
    }
    return <Col, Row, 0>;
}
 
string GetGridTexture(vector grid_pos) {
    // Calculate the texture in the grid to use.
    integer GridCol = llRound(grid_pos.x) / 20;
    integer GridRow = llRound(grid_pos.y) / 10;
 
    // Lookup the texture.
    key Texture = llList2Key(CHARACTER_GRID, GridRow * (GridRow + 1) / 2 + GridCol);
    return Texture;
}
 
vector GetGridOffset(vector grid_pos) {
    // Zoom in on the texture showing our character pair.
    integer Col = llRound(grid_pos.x) % 20;
    integer Row = llRound(grid_pos.y) % 10;
 
    // Return the offset in the texture.
    return <-0.45 + 0.05 * Col, 0.45 - 0.1 * Row, 0.0>;
}
 
ShowChars(integer link,vector grid_pos1, vector grid_pos2, vector grid_pos3, vector grid_pos4, vector grid_pos5) {
  // Set the primitive textures directly.
   
             
  llSetLinkPrimitiveParamsFast( link , [
        PRIM_TEXTURE, FACE_1, GetGridTexture(grid_pos1), <0.25, 0.1, 0>, GetGridOffset(grid_pos1) + <0.075, 0, 0>, 0.0,
        PRIM_TEXTURE, FACE_2, GetGridTexture(grid_pos2), <0.1, 0.1, 0>, GetGridOffset(grid_pos2), 0.0,
        PRIM_TEXTURE, FACE_3, GetGridTexture(grid_pos3), <-1.48, 0.1, 0>, GetGridOffset(grid_pos3)+ <0.37, 0, 0>, 0.0,
        PRIM_TEXTURE, FACE_4, GetGridTexture(grid_pos4), <0.1, 0.1, 0>, GetGridOffset(grid_pos4), 0.0,
        PRIM_TEXTURE, FACE_5, GetGridTexture(grid_pos5), <0.25, 0.1, 0>, GetGridOffset(grid_pos5) - <0.075, 0, 0>, 0.0
        ]);
}
 
RenderString(integer link, string str) {
    // Get the grid positions for each pair of characters.
    vector GridPos1 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)),
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
    vector GridPos2 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)),
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
    vector GridPos3 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)),
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 5, 5)) );
    vector GridPos4 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 6, 6)),
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 7, 7)) );
    vector GridPos5 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 8, 8)),
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 9, 9)) );                                 
 
    // Use these grid positions to display the correct textures/offsets.
    ShowChars(link,GridPos1, GridPos2, GridPos3, GridPos4, GridPos5);
}
 
//RenderWithEffects(integer link, string str) {
//    // Get the grid positions for each pair of characters.
//    vector GridPos1 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)),
//                                  llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
//    vector GridPos2 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)),
//                                  llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
//    vector GridPos3 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)),
//                                  llSubStringIndex(gCharIndex, llGetSubString(str, 5, 5)) );
//    vector GridPos4 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 6, 6)),
//                                  llSubStringIndex(gCharIndex, llGetSubString(str, 7, 7)) );
//    vector GridPos5 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 8, 8)),
//                                  llSubStringIndex(gCharIndex, llGetSubString(str, 9, 9)) );                          //       
//
//      // First set the alpha to the lowest possible.
//  llSetAlpha(0.05, ALL_SIDES);
//
//    // Use these grid positions to display the correct textures/offsets.
//    ShowChars(link,GridPos1, GridPos2, GridPos3, GridPos4, GridPos5);
//
//    float Alpha = 0.10;
//    for (; Alpha <= 1.0; Alpha += 0.05) 
//      llSetAlpha(Alpha, ALL_SIDES);
//          // See if we want to fade out as well.
//  if (gCellHoldDelay < 0.0)
//      // No, bail out. (Just keep showing the string at full strength).
//      return;
//          // Hold the text for a while.
//  llSleep(gCellHoldDelay);
//      // Now fade out.
//  for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
//      llSetAlpha(Alpha, ALL_SIDES);
//          // Make the text transparent to fully hide it.
//  llSetTexture(TRANSPARENT, ALL_SIDES);
//}
 
integer RenderExtended(integer link, string str,integer render) {
    // Look for escape sequences.
    integer length = 0;
    list Parsed      = llParseString2List(str, [], (list)ESCAPE_SEQUENCE);
    integer ParsedLen = llGetListLength(Parsed);
 
    // Create a list of index values to work with.
    list Indices;
    // We start with room for 6 indices.
    integer IndicesLeft = 10;
 
    string Token;
    integer Clipped;
    integer LastWasEscapeSequence = FALSE;
    // Work from left to right.
    integer i=0;
    for (; i < ParsedLen && IndicesLeft > 0; ++i) {
        Token = llList2String(Parsed, i);
 
        // If this is an escape sequence, just set the flag and move on.
        if (Token == ESCAPE_SEQUENCE) {
            LastWasEscapeSequence = TRUE;
        }
        else { // Token != ESCAPE_SEQUENCE
            // Otherwise this is a normal token.  Check its length.
            Clipped = FALSE;
            integer TokenLength = llStringLength(Token);
            // Clip if necessary.
            if (TokenLength > IndicesLeft) {
                TokenLength = llStringLength(Token = llGetSubString(Token, 0, IndicesLeft - 1));
                IndicesLeft = 0;
                Clipped = TRUE;
            }
            else
                IndicesLeft -= TokenLength;
 
            // Was the previous token an escape sequence?
            if (LastWasEscapeSequence) {
                // Yes, the first character is an escape character, the rest are normal.
                length += 2 + TokenLength;
                if (render)
                {
                    // This is the extended character.
                    Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
                   
                    // These are the normal characters.
                    integer j=1;
                    for (; j < TokenLength; ++j)
                    {
                        Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
                    }
                }
            }
            else { // Normal string.
                // Just add the characters normally.
                length += TokenLength;
                if(render)
                {
                    integer j=0;
                    for (; j < TokenLength; ++j)
                    {
                        Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
                    }
                }
            }
 
            // Unset this flag, since this was not an escape sequence.
            LastWasEscapeSequence = FALSE;
        }
    }
 
    if(render)
    {
        // Use the indices to create grid positions.
        vector GridPos1 = GetGridPos( llList2Integer(Indices, 0), llList2Integer(Indices, 1) );
        vector GridPos2 = GetGridPos( llList2Integer(Indices, 2), llList2Integer(Indices, 3) );
        vector GridPos3 = GetGridPos( llList2Integer(Indices, 4), llList2Integer(Indices, 5) );
        vector GridPos4 = GetGridPos( llList2Integer(Indices, 6), llList2Integer(Indices, 7) );
        vector GridPos5 = GetGridPos( llList2Integer(Indices, 8), llList2Integer(Indices, 9) );
       
        // Use these grid positions to display the correct textures/offsets.
        ShowChars(link,GridPos1, GridPos2, GridPos3, GridPos4, GridPos5);
    }
    return length;
}
 
 
integer ConvertIndex(integer index) {
    // This converts from an ASCII based index to our indexing scheme.
    if (index >= 32) // ' ' or higher
        index -= 32;
    else { // index < 32
        // Quick bounds check.
        if (index > 15)
            index = 15;
 
        index += 94; // extended characters
    }
 
    return index;
}
 
 
PassToRender(integer render,string message, integer bank)
{
    float time;
    integer extendedlen = 0;
    integer link;
   
    integer i = 0;
    integer msgLen = llStringLength(message);
    string TextToRender;
    integer num_slaves=llGetListLength(gSlaveNames);
    string slave_name; //avoids unnecessary casts, keeping it as a string
   
 
    //get the bank offset and length
    integer bank_offset=llList2Integer(gBankingData, (bank * BANK_STRIDE));
    integer bank_length=llList2Integer(gBankingData, (bank * BANK_STRIDE) + 1);
    integer bank_highest_dirty=llList2Integer(gBankingData, (bank * BANK_STRIDE) + 2);
 
    integer x=0;   
    for (;x < msgLen;x = x + 10)
    {
 
        if (i >= bank_length)  //we don't want to run off the end of the bank
        {
            //set the dirty to max, and bail out, we're done
            gBankingData=llListReplaceList(gBankingData, [bank_length], (bank * BANK_STRIDE) + 2, (bank * BANK_STRIDE) + 2);
            return;
        } 
       
        link = unpack(gXyTextPrims,(i + bank_offset));
        TextToRender = llGetSubString(message, x, x + 20);
       
        if(gSlaveRegistered && (link % (num_slaves +1)))
        {
            slave_name=llList2String(gSlaveNames, (link % (num_slaves + 1)) - 1);
            if (render == 1)
                llMessageLinked(LINK_THIS, SLAVE_DISPLAY, TextToRender, (key)((string)link + "," + slave_name));
            if (render == 2)
            {
                if(llSubStringIndex(TextToRender,"\e")>x+10)
                {
                    extendedlen = 10;
                }
                else
                {
                    extendedlen = RenderExtended(link,TextToRender,0);
                }
 
                if(extendedlen>10)
                {
                    x += extendedlen-10;
                }
 
                llMessageLinked(LINK_THIS,SLAVE_DISPLAY_EXTENDED,TextToRender,(key)((string)link+","+slave_name));
            }       
        }
        else
        {
            if (render == 1)
                RenderString(link,TextToRender);
            if (render == 2)
            {
                extendedlen = RenderExtended(link,TextToRender,1);
                if(extendedlen>10)
                {
                    x += extendedlen-10;
                }
            }
           
//            if (render == 3)
//                RenderWithEffects(link,TextToRender);
        }
        ++i;           
    }
   
    if (bank_highest_dirty==0)
        bank_highest_dirty=bank_length;
   
    integer current_highest_dirty=i;
    while (i < bank_highest_dirty)
    {
        link = unpack(gXyTextPrims,(i + bank_offset));
       
        if(gSlaveRegistered && (link % (num_slaves+1) != 0))
        {
            slave_name=llList2String(gSlaveNames, (link % (num_slaves + 1)) - 1);
            llMessageLinked(LINK_THIS, SLAVE_DISPLAY, "    ", (key)((string)link + "," + slave_name));     
            //sorry, no fade effect with slave
        }
        else
        {
            RenderString(link,"          ");
        }
        ++i;       
    }
    gBankingData=llListReplaceList(gBankingData, [current_highest_dirty], (bank * BANK_STRIDE) + 2, (bank * BANK_STRIDE) + 2);
}
 
// Bitwise Voodoo by Gigs Taggart and optimized by Strife Onizuka
list gXyTextPrims;
 
 
integer get_number_of_prims()
{//ignores avatars.
    integer a = llGetNumberOfPrims();
    while(llGetAgentSize(llGetLinkKey(a)))
        --a;
    return a;
}
 
//functions to pack 8-bit shorts into ints
list pack_and_insert(list in_list, integer pos, integer value)
{
//    //figure out the bitpack position
//    integer pack = pos & 3; //4 bytes per int
//    pos=pos >> 2;
//    integer shifted = value << (pack << 3);
//    integer old_value = llList2Integer(in_list, pos);
//    shifted = old_value | shifted;
//    in_list = llListReplaceList(in_list, (list)shifted, pos, pos);
//    return in_list;
    //Safe optimized version
    integer index = pos >> 2;
    return llListReplaceList(in_list, (list)(llList2Integer(in_list, index) | (value << ((pos & 3) << 3))), index, index);
}
 
integer unpack(list in_list, integer pos)
{
    return (llList2Integer(in_list, pos >> 2) >> ((pos & 3) << 3)) & 0x000000FF;//unsigned
//    return (llList2Integer(in_list, pos >> 2) << (((~pos) & 3) << 3)) >> 24;//signed
}
 
 
change_color(vector color)
{
    integer num_prims=llGetListLength(gXyTextPrims) << 2;
   
    integer i = 0;
   
    for (; i<=num_prims; ++i)
    {
        integer link = unpack(gXyTextPrims,i);
        if (!link)
            return;
       
        llSetLinkPrimitiveParamsFast( link,[
            PRIM_COLOR, FACE_1, color, 1.0,
            PRIM_COLOR, FACE_2, color, 1.0,
            PRIM_COLOR, FACE_3, color, 1.0,
            PRIM_COLOR, FACE_4, color, 1.0,
            PRIM_COLOR, FACE_5, color, 1.0
        ]);
    }
}
 
change_line_color(integer bank, vector color)
{   
 
    //get the bank offset and length
    integer i = llList2Integer(gBankingData, (bank * BANK_STRIDE));
    integer bank_end = i + llList2Integer(gBankingData, (bank * BANK_STRIDE) + 1);
 
    for (; i < bank_end; ++i)
    {   
        integer link = unpack(gXyTextPrims,i);
        if (!link)
            return;
       
        llSetLinkPrimitiveParamsFast( link,[
            PRIM_COLOR, FACE_1, color, 1.0,
            PRIM_COLOR, FACE_2, color, 1.0,
            PRIM_COLOR, FACE_3, color, 1.0,
            PRIM_COLOR, FACE_4, color, 1.0,
            PRIM_COLOR, FACE_5, color, 1.0
        ]);
    }
}
 
init()
{
    integer num_prims=get_number_of_prims();
    string link_name;
    integer bank=0;
    integer bank_empty=FALSE;
    integer prims_pointer=0; //"pointer" to the next entry to be used in the gXyTextPrims list.
    list temp_bank=[];
    integer temp_bank_stride=2;
    // moving this before the prim scan so that the slaves properly configure themseves before
    // any requests to display
    llMessageLinked(LINK_THIS, SLAVE_RESET, "" , null_key);
 
    gXyTextPrims=[];
    integer x=0;
    for (;x<64;++x)
    {
        gXyTextPrims= (gXyTextPrims = []) + gXyTextPrims + [0];  //we need to pad out the list to make it easier to add things in any order later
    }
 
    gBankingData = [];
    @loop;
    {
        //loop over all prims, looking for ones in the current bank
        for(x=0;x<=num_prims;++x)
        {
            link_name=llGetLinkName(x);
            list tmp = llParseString2List(link_name, ["-"], []);
            if(llList2String(tmp,0)== "xyzzytext")
            {
                integer prims_bank=llList2Integer(tmp,1);
                if (llList2Integer(tmp,1)==bank)
                {
                    temp_bank+=llList2Integer(tmp,2) + (list)x;
                }
            }
        }
        if (temp_bank!=[])
        {
            //sort the current bank
            temp_bank=llListSort(temp_bank, temp_bank_stride, TRUE);
            integer temp_len=llGetListLength(temp_bank);
            //store metadata
            gBankingData+=[prims_pointer,temp_len/temp_bank_stride,0];
            //repack the bank into the prim list
            for (x=0; x < temp_len; x+=temp_bank_stride)
            {
                gXyTextPrims = pack_and_insert(gXyTextPrims, prims_pointer, llList2Integer(temp_bank, x+1));
                ++prims_pointer;
            }
            ++bank;
            temp_bank=[];
            jump loop;
        }
    }
}
 
default {
    state_entry() {
        // Initialize the character index.
        ResetCharIndex();
        init();
    }
 
  on_rez(integer num)
  {
      llResetScript();     
  }
 
    link_message(integer sender, integer channel, string data, key id) {
        if(id == null_key)
            id="0";
       
        if (channel == DISPLAY_STRING) {
            PassToRender(1,data, (integer)((string)id));
            return;
        }
        else if (channel == DISPLAY_EXTENDED) {
            PassToRender(2,data, (integer)((string)id));
            return;
        }
        else if (channel == REMAP_INDICES) {
            // Parse the message, splitting it up into index values.
            list Parsed = llCSV2List(data);
            integer i;
            // Go through the list and swap each pair of indices.
            for (i = 0; i < llGetListLength(Parsed); i += 2) {
                integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
                integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
 
                // Swap these index values.
                string Value1 = llGetSubString(gCharIndex, Index1, Index1);
                string Value2 = llGetSubString(gCharIndex, Index2, Index2);
 
                gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
                gCharIndex = llInsertString(gCharIndex, Index1, Value2);
 
                gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
                gCharIndex = llInsertString(gCharIndex, Index2, Value1);
            }
            return;
        }
        else if (channel == RESET_INDICES) {
            // Restore the character index back to default settings.
            ResetCharIndex();
            return;
        }       
        else if (channel == RESCAN_LINKSET)
        {
            init();
        }
        else if (channel == SET_COLOR) {
            change_color((vector)data);
        }
        else if (channel == SET_LINE_COLOR) {
            change_line_color((integer)((string)id), (vector)data);
        }   
        else if (channel == REGISTER_SLAVE)
        {
            if(!~llListFindList(gSlaveNames, (list)data))
            {//isn't registered yet
                gSlaveNames += data;
                gSlaveRegistered=TRUE;
                //llOwnerSay((string)llGetListLength(gSlaveNames) + " Slave(s) Recognized: " + data);
            }
//            else
//            {//it already exists
//                llOwnerSay((string)llGetListLength(gSlaveNames) + " Slave, Existing Slave Recognized: " + data);
//            }
            llMessageLinked(LINK_THIS, SLAVE_RECOGNIZED, data , null_key);
        }
    }
 
    changed(integer change)
    {
        if(change&CHANGED_INVENTORY)
        {
            if(gSlaveRegistered)       
            {
                //by using negative indexes they don't need to be adjusted when an entry is deleted.
                integer x = ~llGetListLength(gSlaveNames);
                while(++x)
                {
                    if (!~llGetInventoryType(llList2String(gSlaveNames, x)))
                    {
                        //llOwnerSay("Slave Removed: " + llList2String(gSlaveNames, x));
                        gSlaveNames = llDeleteSubList(gSlaveNames, x, x);
                    }
                }
                gSlaveRegistered = !(gSlaveNames == []);
            }
        }
    }
}
</source>
 
== Slave Script ==
 
<source lang="lsl2">
////////////////////////////////////////////
// XyzzyText v2.1(10-Char) by Thraxis Epsilon
// XyText v2.0 SLAVE Script (5 Face, Single Texture)
//
// Heavily Modified by Thraxis Epsilon, Gigs Taggart 5/2007 and Strife Onizuka 8/2007
// Rewrite to allow one-script-per-object operation w/ optional slaves
// Enable prim-label functionality
// Enabled Banking
// Enabled 10-char per prim
//
// Modified by Kermitt Quirk 19/01/2006
// To add support for 5 face prim instead of 3
//
// Originally Written by Xylor Baysklef
//
//
////////////////////////////////////////////
 
 
integer REMAP_INDICES      = 204002;
integer RESET_INDICES      = 204003;
 
//internal API
integer REGISTER_SLAVE      = 205000;
integer SLAVE_RECOGNIZED    = 205001;
integer SLAVE_DISPLAY      = 205003;
integer SET_FONT_TEXTURE    = 204005;
 
integer SLAVE_DISPLAY_EXTENDED = 205004;
integer SLAVE_RESET = 205005;
 
// This is an extended character escape sequence.
string  ESCAPE_SEQUENCE = "\\e";
 
// This is used to get an index for the extended character.
string  EXTENDED_INDEX  = "123456789abcdef";
 
// Face numbers.
integer FACE_1          = 3;
integer FACE_2          = 7;
integer FACE_3          = 4;
integer FACE_4          = 6;
integer FACE_5          = 1;
 
// This is a list of textures for all 2-character combinations.
list    CHARACTER_GRID  = [
        "00e9f9f7-0669-181c-c192-7f8e67678c8d",
        "347a5cb6-0031-7ec0-2fcf-f298eebf3c0e",
        "4e7e689e-37f1-9eca-8596-a958bbd23963",
        "19ea9c21-67ba-8f6f-99db-573b1b877eb1",
        "dde7b412-cda1-652f-6fc2-73f4641f96e1",
        "af6fa3bb-3a6c-9c4f-4bf5-d1c126c830da",
        "a201d3a2-364b-43b6-8686-5881c0f82a94",
        "b674dec8-fead-99e5-c28d-2db8e4c51540",
        "366e05f3-be6b-e5cf-c33b-731dff649caa",
        "75c4925c-0427-dc0c-c71c-e28674ff4d27",
        "dcbe166b-6a97-efb2-fc8e-e5bc6a8b1be6",
        "0dca2feb-fc66-a762-db85-89026a4ecd68",
        "a0fca76f-503a-946b-9336-0a918e886f7a",
        "67fb375d-89a1-5a4f-8c7a-0cd1c066ffc4",
        "300470b2-da34-5470-074c-1b8464ca050c",
        "d1f8e91c-ce2b-d85e-2120-930d3b630946",
        "2a190e44-7b29-dadb-0bff-c31adaf5a170",
        "75d55e71-f6f8-9835-e746-a45f189f30a1",
        "300fac33-2b30-3da3-26bc-e2d70428ec19",
        "0747c776-011a-53ce-13ee-8b5bb9e87c1e",
        "85a855c3-a94f-01ca-33e0-7dde92e727e2",
        "cbc1dab2-2d61-2986-1949-7a5235c954e1",
        "f7aef047-f266-9596-16df-641010edd8e1",
        "4c34ebf7-e5e1-2e1a-579f-e224d9d5e71b",
        "4a69e98c-26a5-ad05-e92e-b5b906ad9ef9",
        "462a9226-2a97-91ac-2d89-57ab33334b78",
        "20b24b3a-8c57-82ee-c6ed-555003f5dbcd",
        "9b481daa-9ea8-a9fa-1ee4-ab9a0d38e217",
        "c231dbdc-c842-15b0-7aa6-6da14745cfdc",
        "c97e3cbb-c9a3-45df-a0ae-955c1f4bf9cf",
        "f1e7d030-ff80-a242-cb69-f6951d4eae3b",
        "ed32d6c4-d733-c0f1-f242-6df1d222220d",
        "88f96a30-dccf-9b20-31ef-da0dfeb23c72",
        "252f2595-58b8-4bcc-6515-fa274d0cfb65",
        "f2838c4f-de80-cced-dff8-195dfdf36b2c",
        "cc2594fe-add2-a3df-cdb3-a61711badf53",
        "e0ce2972-da00-955c-129e-3289b3676776",
        "3e0d336d-321f-ddfa-5c1b-e26131766f6a",
        "d43b1dc4-6b51-76a7-8b90-38865b82bf06",
        "06d16cbb-1868-fd1d-5c93-eae42164a37d",
        "dd5d98cf-273e-3fd0-f030-48be58ee3a0b",
        "0e47c89e-de4a-6233-a2da-cb852aad1b00",
        "fb9c4a55-0e13-495b-25c4-f0b459dc06de",
        "e3ce8def-312c-735b-0e48-018b6799c883",
        "2f713216-4e71-d123-03ed-9c8554710c6b",
        "4a417d8a-1f4f-404b-9783-6672f8527911",
        "ca5e21ec-5b20-5909-4c31-3f90d7316b33",
        "06a4fcc3-e1c4-296d-8817-01f88fbd7367",
        "130ac084-6f3c-95de-b5b6-d25c80703474",
        "59d540a0-ae9d-3606-5ae0-4f2842b64cfa",
        "8612ae9a-f53c-5bf4-2899-8174d7abc4fd",
        "12467401-e979-2c49-34e0-6ac761542797",
        "d53c3eaa-0404-3860-0675-3e375596c3e3",
        "9f5b26bd-81d3-b25e-62fe-5b671d1e3e79",
        "f57f0b64-a050-d617-ee00-c8e9e3adc9cb",
        "beff166a-f5f3-f05e-e020-98f2b00e27ed",
        "02278a65-94ba-6d5e-0d2b-93f2e4f4bf70",
        "a707197d-449e-5b58-846c-0c850c61f9d6",
        "021d4b1a-9503-a44f-ee2b-976eb5d80e68",
        "0ae2ffae-7265-524d-cb76-c2b691992706",
        "f6e41cf2-1104-bd0b-0190-dffad1bac813",
        "2b4bb15e-956d-56ae-69f5-d26a20de0ce7",
        "f816da2c-51f1-612a-2029-a542db7db882",
        "345fea05-c7be-465c-409f-9dcb3bd2aa07"];
list    CHARACTER_GRID2  = [       
        "b3017e02-c063-5185-acd5-1ef5f9d79b89",
        "4dcff365-1971-3c2b-d73c-77e1dc54242a"
          ];
 
///////////// END CONSTANTS ////////////////
string gCharIndex;
 
integer gActive; //if we are recognized, this is true
/////////// END GLOBAL VARIABLES ////////////
ResetCharIndex() {
    gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
    // \" <-- Fixes LSL syntax highlighting bug.
    gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
    gCharIndex += "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n";
}
 
vector GetGridPos(integer index1, integer index2) {
    // There are two ways to use the lookup table...
    integer Col;
    integer Row;
    if (index1 >= index2) {
        // In this case, the row is the index of the first character:
        Row = index1;
        // And the col is the index of the second character (x2)
        Col = index2 * 2;
    }
    else { // Index1 < Index2
        // In this case, the row is the index of the second character:
        Row = index2;
        // And the col is the index of the first character, x2, offset by 1.
        Col = index1 * 2 + 1;
    }
    return <Col, Row, 0>;
}
 
string GetGridTexture(vector grid_pos) {
    // Calculate the texture in the grid to use.
    integer GridCol = llRound(grid_pos.x) / 20;
    integer GridRow = llRound(grid_pos.y) / 10;
 
    // Lookup the texture.
    key Texture = llList2Key(CHARACTER_GRID, GridRow * (GridRow + 1) / 2 + GridCol);
    return Texture;
}
 
vector GetGridOffset(vector grid_pos) {
    // Zoom in on the texture showing our character pair.
    integer Col = llRound(grid_pos.x) % 20;
    integer Row = llRound(grid_pos.y) % 10;
 
    // Return the offset in the texture.
    return <-0.45 + 0.05 * Col, 0.45 - 0.1 * Row, 0.0>;
}
 
ShowChars(integer link,vector grid_pos1, vector grid_pos2, vector grid_pos3, vector grid_pos4, vector grid_pos5) {
  // Set the primitive textures directly.
   
             
  llSetLinkPrimitiveParamsFast( link , [
        PRIM_TEXTURE, FACE_1, GetGridTexture(grid_pos1), <0.25, 0.1, 0>, GetGridOffset(grid_pos1) + <0.075, 0, 0>, 0.0,
        PRIM_TEXTURE, FACE_2, GetGridTexture(grid_pos2), <0.1, 0.1, 0>, GetGridOffset(grid_pos2), 0.0,
        PRIM_TEXTURE, FACE_3, GetGridTexture(grid_pos3), <-1.48, 0.1, 0>, GetGridOffset(grid_pos3)+ <0.37, 0, 0>, 0.0,
        PRIM_TEXTURE, FACE_4, GetGridTexture(grid_pos4), <0.1, 0.1, 0>, GetGridOffset(grid_pos4), 0.0,
        PRIM_TEXTURE, FACE_5, GetGridTexture(grid_pos5), <0.25, 0.1, 0>, GetGridOffset(grid_pos5) - <0.075, 0, 0>, 0.0
        ]);
}
 
RenderString(integer link, string str) {
    // Get the grid positions for each pair of characters.
    vector GridPos1 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)),
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
    vector GridPos2 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)),
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
    vector GridPos3 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)),
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 5, 5)) );
    vector GridPos4 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 6, 6)),
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 7, 7)) );
    vector GridPos5 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 8, 8)),
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 9, 9)) );                                 
 
    // Use these grid positions to display the correct textures/offsets.
    ShowChars(link,GridPos1, GridPos2, GridPos3, GridPos4, GridPos5);
}
 
RenderExtended(integer link, string str) {
    // Look for escape sequences.
    list Parsed      = llParseString2List(str, [], (list)ESCAPE_SEQUENCE);
    integer ParsedLen = llGetListLength(Parsed);
 
    // Create a list of index values to work with.
    list Indices;
    // We start with room for 6 indices.
    integer IndicesLeft = 10;
 
    string Token;
    integer Clipped;
    integer LastWasEscapeSequence = FALSE;
    // Work from left to right.
    integer i=0;
    for (; i < ParsedLen && IndicesLeft > 0; ++i) {
        Token = llList2String(Parsed, i);
 
        // If this is an escape sequence, just set the flag and move on.
        if (Token == ESCAPE_SEQUENCE) {
            LastWasEscapeSequence = TRUE;
        }
        else { // Token != ESCAPE_SEQUENCE
            // Otherwise this is a normal token.  Check its length.
            Clipped = FALSE;
            integer TokenLength = llStringLength(Token);
            // Clip if necessary.
            if (TokenLength > IndicesLeft) {
                TokenLength = llStringLength(Token = llGetSubString(Token, 0, IndicesLeft - 1));
                IndicesLeft = 0;
                Clipped = TRUE;
            }
            else
                IndicesLeft -= TokenLength;
 
            // Was the previous token an escape sequence?
            if (LastWasEscapeSequence) {
                // Yes, the first character is an escape character, the rest are normal.
                // This is the extended character.
                Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
               
                // These are the normal characters.
                integer j=1;
                for (; j < TokenLength; ++j)
                {
                    Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
                }
            }
            else { // Normal string.
                // Just add the characters normally.
                integer j=0;
                for (; j < TokenLength; ++j)
                {
                    Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
                }
            }
 
            // Unset this flag, since this was not an escape sequence.
            LastWasEscapeSequence = FALSE;
        }
    }
 
    // Use the indices to create grid positions.
    vector GridPos1 = GetGridPos( llList2Integer(Indices, 0), llList2Integer(Indices, 1) );
    vector GridPos2 = GetGridPos( llList2Integer(Indices, 2), llList2Integer(Indices, 3) );
    vector GridPos3 = GetGridPos( llList2Integer(Indices, 4), llList2Integer(Indices, 5) );
    vector GridPos4 = GetGridPos( llList2Integer(Indices, 6), llList2Integer(Indices, 7) );
    vector GridPos5 = GetGridPos( llList2Integer(Indices, 8), llList2Integer(Indices, 9) );
   
    // Use these grid positions to display the correct textures/offsets.
    ShowChars(link,GridPos1, GridPos2, GridPos3, GridPos4, GridPos5);
}
 
integer ConvertIndex(integer index) {
    // This converts from an ASCII based index to our indexing scheme.
    if (index >= 32) // ' ' or higher
        index -= 32;
    else { // index < 32
        // Quick bounds check.
        if (index > 15)
            index = 15;
 
        index += 94; // extended characters
    }
 
    return index;
}
 
 
default {
    state_entry()
    {
        // Initialize the character index.
        ResetCharIndex();
        llMessageLinked(LINK_THIS, REGISTER_SLAVE, llGetScriptName() , NULL_KEY); 
    }
 
  on_rez(integer num)
  {
      llResetScript();     
  }
 
    link_message(integer sender, integer channel, string data, key id) {
        if (channel == SLAVE_RECOGNIZED)
        {
            if (data == llGetScriptName())
            {
                gActive=TRUE;
            }
            return;
        }
   
        else if (channel == SLAVE_DISPLAY)
        {
            if (!gActive) 
                return;
               
            list params=llCSV2List((string)id);
            if (llList2String(params, 1) != llGetScriptName())
                return;
           
           
            RenderString(llList2Integer(params, 0),data);
            return;
        }
   
      else if (channel == SLAVE_DISPLAY_EXTENDED)
      {
            if (!gActive) 
                return;
               
            list params=llCSV2List((string)id);
            if (llList2String(params, 1) != llGetScriptName())
                return;
               
            RenderExtended(llList2Integer(params, 0),data);
    }
        else if (channel == REMAP_INDICES) {
            // Parse the message, splitting it up into index values.
            list Parsed = llCSV2List(data);
            integer i;
            // Go through the list and swap each pair of indices.
            for (i = 0; i < llGetListLength(Parsed); i += 2) {
                integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
                integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
 
                // Swap these index values.
                string Value1 = llGetSubString(gCharIndex, Index1, Index1);
                string Value2 = llGetSubString(gCharIndex, Index2, Index2);
 
                gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
                gCharIndex = llInsertString(gCharIndex, Index1, Value2);
 
                gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
                gCharIndex = llInsertString(gCharIndex, Index2, Value1);
            }
            return;
        }
        else if (channel == RESET_INDICES) {
            // Restore the character index back to default settings.
            ResetCharIndex();
            return;
        }               
      else if (channel == SLAVE_RESET)
        {
            ResetCharIndex();
            gActive=FALSE;
            llMessageLinked(LINK_THIS, REGISTER_SLAVE, llGetScriptName() , NULL_KEY);
        }
    }   
}
</source>
 
== 10-Char Prim Setup ==
<source lang="lsl2">
////////////////////////////////////////////
// XyzzyText Prim Setup Script (5 Face)
//
// Modified by Thraxis Epsilon
//
////////////////////////////////////////////
 
default
{
    state_entry()
    {
 
        llSetPrimitiveParams([
                    PRIM_TYPE, PRIM_TYPE_PRISM, 32, <0.199, 0.8, 0.0>, 0.30, <0.0, 0.0, 0.0>, <1.0, 1.0, 0.0>, <0.0, 0.0, 0.0>,
                    PRIM_SIZE, <0.03, 2.89, 0.5>,
                    PRIM_TEXTURE, 1, "09b04244-9569-d21f-6de0-4bbcf5552222", <2.48, 1.0, 0.0>, <-0.74, 0.0, 0.0>, 0.0,
                    PRIM_TEXTURE, 6, "09b04244-9569-d21f-6de0-4bbcf5552222", <1.0, 1.0, 0.0>, <0.0, 0.0, 0.0>, 0.0,
                    PRIM_TEXTURE, 4, "09b04244-9569-d21f-6de0-4bbcf5552222", <14.75, 1.0, 0.0>, <0.27, 0.0, 0.0>, 0.0,
                    PRIM_TEXTURE, 7, "09b04244-9569-d21f-6de0-4bbcf5552222", <1.0, 1.0, 0.0>, <0.0, 0.0, 0.0>, 0.0,
                    PRIM_TEXTURE, 3, "09b04244-9569-d21f-6de0-4bbcf5552222", <2.48, 1.0, 0.0>, <-0.25, 0.0, 0.0>, 0.0]);
                   
        llRemoveInventory(llGetScriptName());
    }
}
</source>
 
= XyzzyText - 16 Character MESH PLANE (Modification by Traven Sachs) =
 
== Master Script ==
<source lang="lsl2">
////////////////////////////////////////////
// XyzzyText v2.1m (16-Char MESH) by Traven Sachs 29-November-2012
// XyzzyText v2.1(10-Char) by Thraxis Epsilon
// XyzzyText v2.1 Script (Set Line Color) by Huney Jewell
// XyzzyText v2.0 Script (5 Face, Single Texture)
//
// Heavily Modified by Thraxis Epsilon, Gigs Taggart 5/2007 and Strife Onizuka 8/2007
// Rewrite to allow one-script-per-object operation w/ optional slaves
// Enable prim-label functionality
// Enabled Banking
// Enabled 10-char per prim
// Enabled 16-Char on an 8 Face Mesh Plane Prim
//
// Modified by Kermitt Quirk 19/01/2006
// To add support for 5 face prim instead of 3
//
// Core XyText Originally Written by Xylor Baysklef
//
////////////////////////////////////////////
 
/////////////// CONSTANTS ///////////////////
// XyText Message Map.
integer DISPLAY_STRING      = 204000;
integer DISPLAY_EXTENDED    = 204001;
integer REMAP_INDICES      = 204002;
integer RESET_INDICES      = 204003;
integer SET_FADE_OPTIONS    = 204004;
integer SET_FONT_TEXTURE    = 204005;
integer SET_LINE_COLOR      = 204006;
integer SET_COLOR          = 204007;
integer RESCAN_LINKSET      = 204008;
 
//internal API
integer REGISTER_SLAVE      = 205000;
integer SLAVE_RECOGNIZED    = 205001;
integer SLAVE_DISPLAY      = 205003;
integer SLAVE_DISPLAY_EXTENDED = 205004;
integer SLAVE_RESET        = 205005;
 
// This is an extended character escape sequence.
string  ESCAPE_SEQUENCE = "\\e";
 
// This is used to get an index for the extended character.
string  EXTENDED_INDEX  = "123456789abcdef";
 
 
// Face numbers.
integer FACE_1          = 0;
integer FACE_2          = 1;
integer FACE_3          = 2;
integer FACE_4          = 3;
integer FACE_5          = 4;
integer FACE_6          = 5;
integer FACE_7          = 6;
integer FACE_8          = 7;
 
 
// Used to hide the text after a fade-out.
key    TRANSPARENT    = "f54a0c32-3cd1-d49a-5b4f-7b792bebc204";
key    null_key        = NULL_KEY;
 
// This is a list of textures for all 2-character combinations.
list    CHARACTER_GRID  = [
        "00e9f9f7-0669-181c-c192-7f8e67678c8d",
        "347a5cb6-0031-7ec0-2fcf-f298eebf3c0e",
        "4e7e689e-37f1-9eca-8596-a958bbd23963",
        "19ea9c21-67ba-8f6f-99db-573b1b877eb1",
        "dde7b412-cda1-652f-6fc2-73f4641f96e1",
        "af6fa3bb-3a6c-9c4f-4bf5-d1c126c830da",
        "a201d3a2-364b-43b6-8686-5881c0f82a94",
        "b674dec8-fead-99e5-c28d-2db8e4c51540",
        "366e05f3-be6b-e5cf-c33b-731dff649caa",
        "75c4925c-0427-dc0c-c71c-e28674ff4d27",
        "dcbe166b-6a97-efb2-fc8e-e5bc6a8b1be6",
        "0dca2feb-fc66-a762-db85-89026a4ecd68",
        "a0fca76f-503a-946b-9336-0a918e886f7a",
        "67fb375d-89a1-5a4f-8c7a-0cd1c066ffc4",
        "300470b2-da34-5470-074c-1b8464ca050c",
        "d1f8e91c-ce2b-d85e-2120-930d3b630946",
        "2a190e44-7b29-dadb-0bff-c31adaf5a170",
        "75d55e71-f6f8-9835-e746-a45f189f30a1",
        "300fac33-2b30-3da3-26bc-e2d70428ec19",
        "0747c776-011a-53ce-13ee-8b5bb9e87c1e",
        "85a855c3-a94f-01ca-33e0-7dde92e727e2",
        "cbc1dab2-2d61-2986-1949-7a5235c954e1",
        "f7aef047-f266-9596-16df-641010edd8e1",
        "4c34ebf7-e5e1-2e1a-579f-e224d9d5e71b",
        "4a69e98c-26a5-ad05-e92e-b5b906ad9ef9",
        "462a9226-2a97-91ac-2d89-57ab33334b78",
        "20b24b3a-8c57-82ee-c6ed-555003f5dbcd",
        "9b481daa-9ea8-a9fa-1ee4-ab9a0d38e217",
        "c231dbdc-c842-15b0-7aa6-6da14745cfdc",
        "c97e3cbb-c9a3-45df-a0ae-955c1f4bf9cf",
        "f1e7d030-ff80-a242-cb69-f6951d4eae3b",
        "ed32d6c4-d733-c0f1-f242-6df1d222220d",
        "88f96a30-dccf-9b20-31ef-da0dfeb23c72",
        "252f2595-58b8-4bcc-6515-fa274d0cfb65",
        "f2838c4f-de80-cced-dff8-195dfdf36b2c",
        "cc2594fe-add2-a3df-cdb3-a61711badf53",
        "e0ce2972-da00-955c-129e-3289b3676776",
        "3e0d336d-321f-ddfa-5c1b-e26131766f6a",
        "d43b1dc4-6b51-76a7-8b90-38865b82bf06",
        "06d16cbb-1868-fd1d-5c93-eae42164a37d",
        "dd5d98cf-273e-3fd0-f030-48be58ee3a0b",
        "0e47c89e-de4a-6233-a2da-cb852aad1b00",
        "fb9c4a55-0e13-495b-25c4-f0b459dc06de",
        "e3ce8def-312c-735b-0e48-018b6799c883",
        "2f713216-4e71-d123-03ed-9c8554710c6b",
        "4a417d8a-1f4f-404b-9783-6672f8527911",
        "ca5e21ec-5b20-5909-4c31-3f90d7316b33",
        "06a4fcc3-e1c4-296d-8817-01f88fbd7367",
        "130ac084-6f3c-95de-b5b6-d25c80703474",
        "59d540a0-ae9d-3606-5ae0-4f2842b64cfa",
        "8612ae9a-f53c-5bf4-2899-8174d7abc4fd",
        "12467401-e979-2c49-34e0-6ac761542797",
        "d53c3eaa-0404-3860-0675-3e375596c3e3",
        "9f5b26bd-81d3-b25e-62fe-5b671d1e3e79",
        "f57f0b64-a050-d617-ee00-c8e9e3adc9cb",
        "beff166a-f5f3-f05e-e020-98f2b00e27ed",
        "02278a65-94ba-6d5e-0d2b-93f2e4f4bf70",
        "a707197d-449e-5b58-846c-0c850c61f9d6",
        "021d4b1a-9503-a44f-ee2b-976eb5d80e68",
        "0ae2ffae-7265-524d-cb76-c2b691992706"];
list    CHARACTER_GRID2  = [       
        "f6e41cf2-1104-bd0b-0190-dffad1bac813",
        "2b4bb15e-956d-56ae-69f5-d26a20de0ce7",
        "f816da2c-51f1-612a-2029-a542db7db882",
        "345fea05-c7be-465c-409f-9dcb3bd2aa07",
        "b3017e02-c063-5185-acd5-1ef5f9d79b89",
        "4dcff365-1971-3c2b-d73c-77e1dc54242a"
          ];
 
///////////// END CONSTANTS ////////////////
 
///////////// GLOBAL VARIABLES ///////////////
// All displayable characters.  Default to ASCII order.
string gCharIndex;
// This is the channel to listen on while acting
// as a cell in a larger display.
integer gCellChannel      = -1;
// This is the starting character position in the cell channel message
// to render.
integer gCellCharPosition = 0;
// This is whether or not to use the fade in/out special effect.
integer gCellUseFading      = FALSE;
// This is how long to display the text before fading out (if using
// fading special effect).
// Note: < 0  means don't fade out.
float  gCellHoldDelay      = 1.0;
 
integer gSlaveRegistered;
list gSlaveNames;
 
integer BANK_STRIDE = 3; //offset, length, highest_dirty
list gBankingData;
 
/////////// END GLOBAL VARIABLES ////////////
 
ResetCharIndex() {
    gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
    // \" <-- Fixes LSL syntax highlighting bug.
    gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
    gCharIndex += "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n";
}
 
vector GetGridPos(integer index1, integer index2) {
    // There are two ways to use the lookup table...
    integer Col;
    integer Row;
    if (index1 >= index2) {
        // In this case, the row is the index of the first character:
        Row = index1;
        // And the col is the index of the second character (x2)
        Col = index2 * 2;
    }
    else { // Index1 < Index2
        // In this case, the row is the index of the second character:
        Row = index2;
        // And the col is the index of the first character, x2, offset by 1.
        Col = index1 * 2 + 1;
    }
    return <Col, Row, 0>;
}
 
string GetGridTexture(vector grid_pos) {
    // Calculate the texture in the grid to use.
    integer GridCol = llRound(grid_pos.x) / 20;
    integer GridRow = llRound(grid_pos.y) / 10;
 
    // Lookup the texture.
    key Texture = llList2Key(CHARACTER_GRID, GridRow * (GridRow + 1) / 2 + GridCol);
    return Texture;
}
 
vector GetGridOffset(vector grid_pos) {
    // Zoom in on the texture showing our character pair.
    integer Col = llRound(grid_pos.x) % 20;
    integer Row = llRound(grid_pos.y) % 10;
 
    // Return the offset in the texture.
    return <-0.45 + 0.05 * Col, 0.45 - 0.1 * Row, 0.0>;
}
 
ShowChars(integer link,vector grid_pos1, vector grid_pos2, vector grid_pos3, vector grid_pos4, vector grid_pos5, vector grid_pos6, vector grid_pos7, vector grid_pos8) {
  // Set the primitive textures directly.
   
//integer face, string name, vector repeats, vector offsets, float rotation               
  llSetLinkPrimitiveParamsFast( link , [
        PRIM_TEXTURE, FACE_1, GetGridTexture(grid_pos1), <0.1, 0.1, 0>, GetGridOffset(grid_pos1), 0.0,
        PRIM_TEXTURE, FACE_2, GetGridTexture(grid_pos2), <0.1, 0.1, 0>, GetGridOffset(grid_pos2), 0.0,
        PRIM_TEXTURE, FACE_3, GetGridTexture(grid_pos3), <0.1, 0.1, 0>, GetGridOffset(grid_pos3), 0.0,
        PRIM_TEXTURE, FACE_4, GetGridTexture(grid_pos4), <0.1, 0.1, 0>, GetGridOffset(grid_pos4), 0.0,
        PRIM_TEXTURE, FACE_5, GetGridTexture(grid_pos5), <0.1, 0.1, 0>, GetGridOffset(grid_pos5), 0.0,
        PRIM_TEXTURE, FACE_6, GetGridTexture(grid_pos6), <0.1, 0.1, 0>, GetGridOffset(grid_pos6), 0.0,
        PRIM_TEXTURE, FACE_7, GetGridTexture(grid_pos7), <0.1, 0.1, 0>, GetGridOffset(grid_pos7), 0.0,
        PRIM_TEXTURE, FACE_8, GetGridTexture(grid_pos8), <0.1, 0.1, 0>, GetGridOffset(grid_pos8), 0.0
        ]);
}
 
RenderString(integer link, string str) {
    // Get the grid positions for each pair of characters.
    vector GridPos1 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)),
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
    vector GridPos2 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)),
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
    vector GridPos3 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)),
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 5, 5)) );
    vector GridPos4 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 6, 6)),
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 7, 7)) );
    vector GridPos5 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 8, 8)),
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 9, 9)) );
    vector GridPos6 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 10, 10)),
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 11, 11)) );
    vector GridPos7 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 12, 12)),
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 13, 13)) );
    vector GridPos8 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 14, 14)),
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 15, 15)) );                                 
 
    // Use these grid positions to display the correct textures/offsets.
    ShowChars(link,GridPos1, GridPos2, GridPos3, GridPos4, GridPos5, GridPos6, GridPos7, GridPos8);
}
 
//RenderWithEffects(integer link, string str) {
//    // Get the grid positions for each pair of characters.
//    vector GridPos1 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)),
//                                  llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
//    vector GridPos2 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)),
//                                  llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
//    vector GridPos3 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)),
//                                  llSubStringIndex(gCharIndex, llGetSubString(str, 5, 5)) );
//    vector GridPos4 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 6, 6)),
//                                  llSubStringIndex(gCharIndex, llGetSubString(str, 7, 7)) );
//    vector GridPos5 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 8, 8)),
//                                  llSubStringIndex(gCharIndex, llGetSubString(str, 9, 9)) );                          //       
//
//      // First set the alpha to the lowest possible.
//  llSetAlpha(0.05, ALL_SIDES);
//
//    // Use these grid positions to display the correct textures/offsets.
//    ShowChars(link,GridPos1, GridPos2, GridPos3, GridPos4, GridPos5);
//
//    float Alpha = 0.10;
//    for (; Alpha <= 1.0; Alpha += 0.05) 
//      llSetAlpha(Alpha, ALL_SIDES);
//          // See if we want to fade out as well.
//  if (gCellHoldDelay < 0.0)
//      // No, bail out. (Just keep showing the string at full strength).
//      return;
//          // Hold the text for a while.
//  llSleep(gCellHoldDelay);
//      // Now fade out.
//  for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05)
//      llSetAlpha(Alpha, ALL_SIDES);
//          // Make the text transparent to fully hide it.
//  llSetTexture(TRANSPARENT, ALL_SIDES);
//}
 
integer RenderExtended(integer link, string str,integer render) {
    // Look for escape sequences.
    integer length = 0;
    list Parsed      = llParseString2List(str, [], (list)ESCAPE_SEQUENCE);
    integer ParsedLen = llGetListLength(Parsed);
 
    // Create a list of index values to work with.
    list Indices;
    // We start with room for 6 indices.
    integer IndicesLeft = 16;
 
    string Token;
    integer Clipped;
    integer LastWasEscapeSequence = FALSE;
    // Work from left to right.
    integer i=0;
    for (; i < ParsedLen && IndicesLeft > 0; ++i) {
        Token = llList2String(Parsed, i);
 
        // If this is an escape sequence, just set the flag and move on.
        if (Token == ESCAPE_SEQUENCE) {
            LastWasEscapeSequence = TRUE;
        }
        else { // Token != ESCAPE_SEQUENCE
            // Otherwise this is a normal token.  Check its length.
            Clipped = FALSE;
            integer TokenLength = llStringLength(Token);
            // Clip if necessary.
            if (TokenLength > IndicesLeft) {
                TokenLength = llStringLength(Token = llGetSubString(Token, 0, IndicesLeft - 1));
                IndicesLeft = 0;
                Clipped = TRUE;
            }
            else
                IndicesLeft -= TokenLength;
 
            // Was the previous token an escape sequence?
            if (LastWasEscapeSequence) {
                // Yes, the first character is an escape character, the rest are normal.
                length += 2 + TokenLength;
                if (render)
                {
                    // This is the extended character.
                    Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
                   
                    // These are the normal characters.
                    integer j=1;
                    for (; j < TokenLength; ++j)
                    {
                        Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
                    }
                }
            }
            else { // Normal string.
                // Just add the characters normally.
                length += TokenLength;
                if(render)
                {
                    integer j=0;
                    for (; j < TokenLength; ++j)
                    {
                        Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
                    }
                }
            }
 
            // Unset this flag, since this was not an escape sequence.
            LastWasEscapeSequence = FALSE;
        }
    }
 
    if(render)
    {
        // Use the indices to create grid positions.
        vector GridPos1 = GetGridPos( llList2Integer(Indices, 0), llList2Integer(Indices, 1) );
        vector GridPos2 = GetGridPos( llList2Integer(Indices, 2), llList2Integer(Indices, 3) );
        vector GridPos3 = GetGridPos( llList2Integer(Indices, 4), llList2Integer(Indices, 5) );
        vector GridPos4 = GetGridPos( llList2Integer(Indices, 6), llList2Integer(Indices, 7) );
        vector GridPos5 = GetGridPos( llList2Integer(Indices, 8), llList2Integer(Indices, 9) );
        vector GridPos6 = GetGridPos( llList2Integer(Indices, 10), llList2Integer(Indices, 11) );
        vector GridPos7 = GetGridPos( llList2Integer(Indices, 12), llList2Integer(Indices, 13) );
        vector GridPos8 = GetGridPos( llList2Integer(Indices, 14), llList2Integer(Indices, 15) );
       
        // Use these grid positions to display the correct textures/offsets.
        ShowChars(link, GridPos1, GridPos2, GridPos3, GridPos4, GridPos5, GridPos6, GridPos7, GridPos8);
    }
    return length;
}
 
 
integer ConvertIndex(integer index) {
    // This converts from an ASCII based index to our indexing scheme.
    if (index >= 32) // ' ' or higher
        index -= 32;
    else { // index < 32
        // Quick bounds check.
        if (index > 15)
            index = 15;
 
        index += 94; // extended characters
    }
 
    return index;
}
 
 
PassToRender(integer render,string message, integer bank)
{
    float time;
    integer extendedlen = 0;
    integer link;
   
    integer i = 0;
    integer msgLen = llStringLength(message);
    string TextToRender;
    integer num_slaves=llGetListLength(gSlaveNames);
    string slave_name; //avoids unnecessary casts, keeping it as a string
   
 
    //get the bank offset and length
    integer bank_offset=llList2Integer(gBankingData, (bank * BANK_STRIDE));
    integer bank_length=llList2Integer(gBankingData, (bank * BANK_STRIDE) + 1);
    integer bank_highest_dirty=llList2Integer(gBankingData, (bank * BANK_STRIDE) + 2);
 
    integer x=0;   
    for (;x < msgLen;x = x + 16)
    {
 
        if (i >= bank_length)  //we don't want to run off the end of the bank
        {
            //set the dirty to max, and bail out, we're done
            gBankingData=llListReplaceList(gBankingData, [bank_length], (bank * BANK_STRIDE) + 2, (bank * BANK_STRIDE) + 2);
            return;
        } 
       
        link = unpack(gXyTextPrims,(i + bank_offset));
        TextToRender = llGetSubString(message, x, x + 32);
       
        if(gSlaveRegistered && (link % (num_slaves +1)))
        {
            slave_name=llList2String(gSlaveNames, (link % (num_slaves + 1)) - 1);
            if (render == 1)
                llMessageLinked(LINK_THIS, SLAVE_DISPLAY, TextToRender, (key)((string)link + "," + slave_name));
            if (render == 2)
            {
                if(llSubStringIndex(TextToRender,"\e")>x+16)
                {
                    extendedlen = 16;
                }
                else
                {
                    extendedlen = RenderExtended(link,TextToRender,0);
                }
 
                if(extendedlen>16)
                {
                    x += extendedlen-16;
                }
 
                llMessageLinked(LINK_THIS,SLAVE_DISPLAY_EXTENDED,TextToRender,(key)((string)link+","+slave_name));
            }       
        }
        else
        {
            if (render == 1)
                RenderString(link,TextToRender);
            if (render == 2)
            {
                extendedlen = RenderExtended(link,TextToRender,1);
                if(extendedlen>16)
                {
                    x += extendedlen-16;
                }
            }
           
//            if (render == 3)
//                RenderWithEffects(link,TextToRender);
        }
        ++i;           
    }
   
    if (bank_highest_dirty==0)
        bank_highest_dirty=bank_length;
   
    integer current_highest_dirty=i;
    while (i < bank_highest_dirty)
    {
        link = unpack(gXyTextPrims,(i + bank_offset));
       
        if(gSlaveRegistered && (link % (num_slaves+1) != 0))
        {
            slave_name=llList2String(gSlaveNames, (link % (num_slaves + 1)) - 1);
            llMessageLinked(LINK_THIS, SLAVE_DISPLAY, "    ", (key)((string)link + "," + slave_name));     
            //sorry, no fade effect with slave
        }
        else
        {
            RenderString(link,"          ");
        }
        ++i;       
    }
    gBankingData=llListReplaceList(gBankingData, [current_highest_dirty], (bank * BANK_STRIDE) + 2, (bank * BANK_STRIDE) + 2);
}
 
// Bitwise Voodoo by Gigs Taggart and optimized by Strife Onizuka
list gXyTextPrims;
 
 
integer get_number_of_prims()
{//ignores avatars.
    integer a = llGetNumberOfPrims();
    while(llGetAgentSize(llGetLinkKey(a)))
        --a;
    return a;
}
 
//functions to pack 8-bit shorts into ints
list pack_and_insert(list in_list, integer pos, integer value)
{
//    //figure out the bitpack position
//    integer pack = pos & 3; //4 bytes per int
//    pos=pos >> 2;
//    integer shifted = value << (pack << 3);
//    integer old_value = llList2Integer(in_list, pos);
//    shifted = old_value | shifted;
//    in_list = llListReplaceList(in_list, (list)shifted, pos, pos);
//    return in_list;
    //Safe optimized version
    integer index = pos >> 2;
    return llListReplaceList(in_list, (list)(llList2Integer(in_list, index) | (value << ((pos & 3) << 3))), index, index);
}
 
integer unpack(list in_list, integer pos)
{
    return (llList2Integer(in_list, pos >> 2) >> ((pos & 3) << 3)) & 0x000000FF;//unsigned
//    return (llList2Integer(in_list, pos >> 2) << (((~pos) & 3) << 3)) >> 24;//signed
}
 
 
change_color(vector color)
{
    integer num_prims=llGetListLength(gXyTextPrims) << 2;
   
    integer i = 0;
   
    for (; i<=num_prims; ++i)
    {
        integer link = unpack(gXyTextPrims,i);
        if (!link)
            return;
       
        llSetLinkPrimitiveParamsFast( link,[
            PRIM_COLOR, FACE_1, color, 1.0,
            PRIM_COLOR, FACE_2, color, 1.0,
            PRIM_COLOR, FACE_3, color, 1.0,
            PRIM_COLOR, FACE_4, color, 1.0,
            PRIM_COLOR, FACE_5, color, 1.0,
            PRIM_COLOR, FACE_6, color, 1.0,
            PRIM_COLOR, FACE_7, color, 1.0,
            PRIM_COLOR, FACE_8, color, 1.0           
        ]);
    }
}
 
change_line_color(integer bank, vector color)
{   
 
    //get the bank offset and length
    integer i = llList2Integer(gBankingData, (bank * BANK_STRIDE));
    integer bank_end = i + llList2Integer(gBankingData, (bank * BANK_STRIDE) + 1);
 
    for (; i < bank_end; ++i)
    {   
        integer link = unpack(gXyTextPrims,i);
        if (!link)
            return;
       
        llSetLinkPrimitiveParamsFast( link,[
            PRIM_COLOR, FACE_1, color, 1.0,
            PRIM_COLOR, FACE_2, color, 1.0,
            PRIM_COLOR, FACE_3, color, 1.0,
            PRIM_COLOR, FACE_4, color, 1.0,
            PRIM_COLOR, FACE_5, color, 1.0,
            PRIM_COLOR, FACE_6, color, 1.0,
            PRIM_COLOR, FACE_7, color, 1.0,
            PRIM_COLOR, FACE_8, color, 1.0
        ]);
    }
}
 
init()
{
    integer num_prims=get_number_of_prims();
 
    string link_name;
    integer bank=0;
    integer bank_empty=FALSE;
    integer prims_pointer=0; //"pointer" to the next entry to be used in the gXyTextPrims list.
   
    list temp_bank=[];
    integer temp_bank_stride=2;
   
 
    gXyTextPrims=[];
    integer x=0;
    for (;x<64;++x)
    {
        gXyTextPrims= (gXyTextPrims = []) + gXyTextPrims + [0];  //we need to pad out the list to make it easier to add things in any order later
    }
   
    @loop;
   
    {
     
        //loop over all prims, looking for ones in the current bank
        for(x=0;x<=num_prims;++x)
        {
            link_name=llGetLinkName(x);
            list tmp = llParseString2List(link_name, ["-"], []);
            if(llList2String(tmp,0)== "xyzzytext")
            {
                integer prims_bank=llList2Integer(tmp,1);
                if (llList2Integer(tmp,1)==bank)
                {
                    temp_bank+=llList2Integer(tmp,2) + (list)x;
                }
            }
           
        }
 
   
        if (temp_bank!=[])
        {
            //sort the current bank
            temp_bank=llListSort(temp_bank, temp_bank_stride, TRUE);
           
            integer temp_len=llGetListLength(temp_bank);
           
            //store metadata
            gBankingData+=[prims_pointer,temp_len/temp_bank_stride,0];
           
            //repack the bank into the prim list
            for (x=0; x < temp_len; x+=temp_bank_stride)
            {
                gXyTextPrims = pack_and_insert(gXyTextPrims, prims_pointer, llList2Integer(temp_bank, x+1));
                ++prims_pointer;
            }
            ++bank;
            temp_bank=[];
            jump loop;
        }
    }
   
    llMessageLinked(LINK_THIS, SLAVE_RESET, "" , null_key);
}
 
default {
    state_entry() {
        // Initialize the character index.
        ResetCharIndex();
        init();
    }
 
  on_rez(integer num)
  {
      llResetScript();     
  }
 
    link_message(integer sender, integer channel, string data, key id) {
        if(id == null_key)
            id="0";
       
        if (channel == DISPLAY_STRING) {
            PassToRender(1,data, (integer)((string)id));
            return;
        }
        else if (channel == DISPLAY_EXTENDED) {
            PassToRender(2,data, (integer)((string)id));
            return;
        }
        else if (channel == REMAP_INDICES) {
            // Parse the message, splitting it up into index values.
            list Parsed = llCSV2List(data);
            integer i;
            // Go through the list and swap each pair of indices.
            for (i = 0; i < llGetListLength(Parsed); i += 2) {
                integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
                integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
 
                // Swap these index values.
                string Value1 = llGetSubString(gCharIndex, Index1, Index1);
                string Value2 = llGetSubString(gCharIndex, Index2, Index2);
 
                gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
                gCharIndex = llInsertString(gCharIndex, Index1, Value2);
 
                gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
                gCharIndex = llInsertString(gCharIndex, Index2, Value1);
            }
            return;
        }
        else if (channel == RESET_INDICES) {
            // Restore the character index back to default settings.
            ResetCharIndex();
            return;
        }       
        else if (channel == RESCAN_LINKSET)
        {
            init();
        }
        else if (channel == SET_COLOR) {
            change_color((vector)data);
        }
        else if (channel == SET_LINE_COLOR) {
            change_line_color((integer)((string)id), (vector)data);
        }   
        else if (channel == REGISTER_SLAVE)
        {
            if(!~llListFindList(gSlaveNames, (list)data))
            {//isn't registered yet
                gSlaveNames += data;
                gSlaveRegistered=TRUE;
                //llOwnerSay((string)llGetListLength(gSlaveNames) + " Slave(s) Recognized: " + data);
            }
//            else
//            {//it already exists
//                llOwnerSay((string)llGetListLength(gSlaveNames) + " Slave, Existing Slave Recognized: " + data);
//            }
            llMessageLinked(LINK_THIS, SLAVE_RECOGNIZED, data , null_key);
        }
    }
 
    changed(integer change)
    {
        if(change&CHANGED_INVENTORY)
        {
            if(gSlaveRegistered)       
            {
                //by using negative indexes they don't need to be adjusted when an entry is deleted.
                integer x = ~llGetListLength(gSlaveNames);
                while(++x)
                {
                    if (!~llGetInventoryType(llList2String(gSlaveNames, x)))
                    {
                        //llOwnerSay("Slave Removed: " + llList2String(gSlaveNames, x));
                        gSlaveNames = llDeleteSubList(gSlaveNames, x, x);
                    }
                }
                gSlaveRegistered = !(gSlaveNames == []);
            }
        }
    }
}
</source>
 
== Slave Script ==
 
<source lang="lsl2">
////////////////////////////////////////////
// XyzzyText v2.1m (16-Char MESH) by Traven Sachs 29-November-2012
// XyzzyText v2.1(10-Char) by Thraxis Epsilon
// XyText v2.0 SLAVE Script (5 Face, Single Texture)
//
// Heavily Modified by Thraxis Epsilon, Gigs Taggart 5/2007 and Strife Onizuka 8/2007
// Rewrite to allow one-script-per-object operation w/ optional slaves
// Enable prim-label functionality
// Enabled Banking
// Enabled 10-char per prim
// Enabled 16-Char on an 8 Face Mesh Plane Prim
//
// Modified by Kermitt Quirk 19/01/2006
// To add support for 5 face prim instead of 3
//
// Originally Written by Xylor Baysklef
//
//
////////////////////////////////////////////
 
 
integer REMAP_INDICES      = 204002;
integer RESET_INDICES      = 204003;
 
//internal API
integer REGISTER_SLAVE      = 205000;
integer SLAVE_RECOGNIZED    = 205001;
integer SLAVE_DISPLAY      = 205003;
integer SET_FONT_TEXTURE    = 204005;
 
integer SLAVE_DISPLAY_EXTENDED = 205004;
integer SLAVE_RESET = 205005;
 
// This is an extended character escape sequence.
string  ESCAPE_SEQUENCE = "\\e";
 
// This is used to get an index for the extended character.
string  EXTENDED_INDEX  = "123456789abcdef";
 
// Face numbers.
integer FACE_1          = 0;
integer FACE_2          = 1;
integer FACE_3          = 2;
integer FACE_4          = 3;
integer FACE_5          = 4;
integer FACE_6          = 5;
integer FACE_7          = 6;
integer FACE_8          = 7;
 
// This is a list of textures for all 2-character combinations.
list    CHARACTER_GRID  = [
        "00e9f9f7-0669-181c-c192-7f8e67678c8d",
        "347a5cb6-0031-7ec0-2fcf-f298eebf3c0e",
        "4e7e689e-37f1-9eca-8596-a958bbd23963",
        "19ea9c21-67ba-8f6f-99db-573b1b877eb1",
        "dde7b412-cda1-652f-6fc2-73f4641f96e1",
        "af6fa3bb-3a6c-9c4f-4bf5-d1c126c830da",
        "a201d3a2-364b-43b6-8686-5881c0f82a94",
        "b674dec8-fead-99e5-c28d-2db8e4c51540",
        "366e05f3-be6b-e5cf-c33b-731dff649caa",
        "75c4925c-0427-dc0c-c71c-e28674ff4d27",
        "dcbe166b-6a97-efb2-fc8e-e5bc6a8b1be6",
        "0dca2feb-fc66-a762-db85-89026a4ecd68",
        "a0fca76f-503a-946b-9336-0a918e886f7a",
        "67fb375d-89a1-5a4f-8c7a-0cd1c066ffc4",
        "300470b2-da34-5470-074c-1b8464ca050c",
        "d1f8e91c-ce2b-d85e-2120-930d3b630946",
        "2a190e44-7b29-dadb-0bff-c31adaf5a170",
        "75d55e71-f6f8-9835-e746-a45f189f30a1",
        "300fac33-2b30-3da3-26bc-e2d70428ec19",
        "0747c776-011a-53ce-13ee-8b5bb9e87c1e",
        "85a855c3-a94f-01ca-33e0-7dde92e727e2",
        "cbc1dab2-2d61-2986-1949-7a5235c954e1",
        "f7aef047-f266-9596-16df-641010edd8e1",
        "4c34ebf7-e5e1-2e1a-579f-e224d9d5e71b",
        "4a69e98c-26a5-ad05-e92e-b5b906ad9ef9",
        "462a9226-2a97-91ac-2d89-57ab33334b78",
        "20b24b3a-8c57-82ee-c6ed-555003f5dbcd",
        "9b481daa-9ea8-a9fa-1ee4-ab9a0d38e217",
        "c231dbdc-c842-15b0-7aa6-6da14745cfdc",
        "c97e3cbb-c9a3-45df-a0ae-955c1f4bf9cf",
        "f1e7d030-ff80-a242-cb69-f6951d4eae3b",
        "ed32d6c4-d733-c0f1-f242-6df1d222220d",
        "88f96a30-dccf-9b20-31ef-da0dfeb23c72",
        "252f2595-58b8-4bcc-6515-fa274d0cfb65",
        "f2838c4f-de80-cced-dff8-195dfdf36b2c",
        "cc2594fe-add2-a3df-cdb3-a61711badf53",
        "e0ce2972-da00-955c-129e-3289b3676776",
        "3e0d336d-321f-ddfa-5c1b-e26131766f6a",
        "d43b1dc4-6b51-76a7-8b90-38865b82bf06",
        "06d16cbb-1868-fd1d-5c93-eae42164a37d",
        "dd5d98cf-273e-3fd0-f030-48be58ee3a0b",
        "0e47c89e-de4a-6233-a2da-cb852aad1b00",
        "fb9c4a55-0e13-495b-25c4-f0b459dc06de",
        "e3ce8def-312c-735b-0e48-018b6799c883",
        "2f713216-4e71-d123-03ed-9c8554710c6b",
        "4a417d8a-1f4f-404b-9783-6672f8527911",
        "ca5e21ec-5b20-5909-4c31-3f90d7316b33",
        "06a4fcc3-e1c4-296d-8817-01f88fbd7367",
        "130ac084-6f3c-95de-b5b6-d25c80703474",
        "59d540a0-ae9d-3606-5ae0-4f2842b64cfa",
        "8612ae9a-f53c-5bf4-2899-8174d7abc4fd",
        "12467401-e979-2c49-34e0-6ac761542797",
        "d53c3eaa-0404-3860-0675-3e375596c3e3",
        "9f5b26bd-81d3-b25e-62fe-5b671d1e3e79",
        "f57f0b64-a050-d617-ee00-c8e9e3adc9cb",
        "beff166a-f5f3-f05e-e020-98f2b00e27ed",
        "02278a65-94ba-6d5e-0d2b-93f2e4f4bf70",
        "a707197d-449e-5b58-846c-0c850c61f9d6",
        "021d4b1a-9503-a44f-ee2b-976eb5d80e68",
        "0ae2ffae-7265-524d-cb76-c2b691992706",
        "f6e41cf2-1104-bd0b-0190-dffad1bac813",
        "2b4bb15e-956d-56ae-69f5-d26a20de0ce7",
        "f816da2c-51f1-612a-2029-a542db7db882",
        "345fea05-c7be-465c-409f-9dcb3bd2aa07"];
list    CHARACTER_GRID2  = [       
        "b3017e02-c063-5185-acd5-1ef5f9d79b89",
        "4dcff365-1971-3c2b-d73c-77e1dc54242a"
          ];
 
///////////// END CONSTANTS ////////////////
string gCharIndex;
 
integer gActive; //if we are recognized, this is true
/////////// END GLOBAL VARIABLES ////////////
ResetCharIndex() {
    gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`";
    // \" <-- Fixes LSL syntax highlighting bug.
    gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~";
    gCharIndex += "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n";
}
 
vector GetGridPos(integer index1, integer index2) {
    // There are two ways to use the lookup table...
    integer Col;
    integer Row;
    if (index1 >= index2) {
        // In this case, the row is the index of the first character:
        Row = index1;
        // And the col is the index of the second character (x2)
        Col = index2 * 2;
    }
    else { // Index1 < Index2
        // In this case, the row is the index of the second character:
        Row = index2;
        // And the col is the index of the first character, x2, offset by 1.
        Col = index1 * 2 + 1;
    }
    return <Col, Row, 0>;
}
 
string GetGridTexture(vector grid_pos) {
    // Calculate the texture in the grid to use.
    integer GridCol = llRound(grid_pos.x) / 20;
    integer GridRow = llRound(grid_pos.y) / 10;
 
    // Lookup the texture.
    key Texture = llList2Key(CHARACTER_GRID, GridRow * (GridRow + 1) / 2 + GridCol);
    return Texture;
}
 
vector GetGridOffset(vector grid_pos) {
    // Zoom in on the texture showing our character pair.
    integer Col = llRound(grid_pos.x) % 20;
    integer Row = llRound(grid_pos.y) % 10;
 
    // Return the offset in the texture.
    return <-0.45 + 0.05 * Col, 0.45 - 0.1 * Row, 0.0>;
}
 
ShowChars(integer link,vector grid_pos1, vector grid_pos2, vector grid_pos3, vector grid_pos4, vector grid_pos5, vector grid_pos6, vector grid_pos7, vector grid_pos8) {
  // Set the primitive textures directly.
   
             
  llSetLinkPrimitiveParamsFast( link , [
        PRIM_TEXTURE, FACE_1, GetGridTexture(grid_pos1), <0.1, 0.1, 0>, GetGridOffset(grid_pos1), 0.0,
        PRIM_TEXTURE, FACE_2, GetGridTexture(grid_pos2), <0.1, 0.1, 0>, GetGridOffset(grid_pos2), 0.0,
        PRIM_TEXTURE, FACE_3, GetGridTexture(grid_pos3), <0.1, 0.1, 0>, GetGridOffset(grid_pos3), 0.0,
        PRIM_TEXTURE, FACE_4, GetGridTexture(grid_pos4), <0.1, 0.1, 0>, GetGridOffset(grid_pos4), 0.0,
        PRIM_TEXTURE, FACE_5, GetGridTexture(grid_pos5), <0.1, 0.1, 0>, GetGridOffset(grid_pos5), 0.0,
        PRIM_TEXTURE, FACE_6, GetGridTexture(grid_pos6), <0.1, 0.1, 0>, GetGridOffset(grid_pos6), 0.0,
        PRIM_TEXTURE, FACE_7, GetGridTexture(grid_pos7), <0.1, 0.1, 0>, GetGridOffset(grid_pos7), 0.0,
        PRIM_TEXTURE, FACE_8, GetGridTexture(grid_pos8), <0.1, 0.1, 0>, GetGridOffset(grid_pos8), 0.0
        ]);
}
 
RenderString(integer link, string str) {
    // Get the grid positions for each pair of characters.
    vector GridPos1 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)),
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) );
    vector GridPos2 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)),
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) );
    vector GridPos3 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)),
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 5, 5)) );
    vector GridPos4 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 6, 6)),
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 7, 7)) );
    vector GridPos5 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 8, 8)),
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 9, 9)) );
    vector GridPos6 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 10, 10)),
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 11, 11)) );
    vector GridPos7 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 12, 12)),
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 13, 13)) );
    vector GridPos8 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 14, 14)),
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 15, 15)) );                                 
 
    // Use these grid positions to display the correct textures/offsets.
    ShowChars(link,GridPos1, GridPos2, GridPos3, GridPos4, GridPos5, GridPos6, GridPos7, GridPos8);
}
 
RenderExtended(integer link, string str) {
    // Look for escape sequences.
    list Parsed      = llParseString2List(str, [], (list)ESCAPE_SEQUENCE);
    integer ParsedLen = llGetListLength(Parsed);
 
    // Create a list of index values to work with.
    list Indices;
    // We start with room for 16 indices.
    integer IndicesLeft = 16;
 
    string Token;
    integer Clipped;
    integer LastWasEscapeSequence = FALSE;
    // Work from left to right.
    integer i=0;
    for (; i < ParsedLen && IndicesLeft > 0; ++i) {
        Token = llList2String(Parsed, i);
 
        // If this is an escape sequence, just set the flag and move on.
        if (Token == ESCAPE_SEQUENCE) {
            LastWasEscapeSequence = TRUE;
        }
        else { // Token != ESCAPE_SEQUENCE
            // Otherwise this is a normal token.  Check its length.
            Clipped = FALSE;
            integer TokenLength = llStringLength(Token);
            // Clip if necessary.
            if (TokenLength > IndicesLeft) {
                TokenLength = llStringLength(Token = llGetSubString(Token, 0, IndicesLeft - 1));
                IndicesLeft = 0;
                Clipped = TRUE;
            }
            else
                IndicesLeft -= TokenLength;
 
            // Was the previous token an escape sequence?
            if (LastWasEscapeSequence) {
                // Yes, the first character is an escape character, the rest are normal.
                // This is the extended character.
                Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
               
                // These are the normal characters.
                integer j=1;
                for (; j < TokenLength; ++j)
                {
                    Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
                }
            }
            else { // Normal string.
                // Just add the characters normally.
                integer j=0;
                for (; j < TokenLength; ++j)
                {
                    Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
                }
            }
 
            // Unset this flag, since this was not an escape sequence.
            LastWasEscapeSequence = FALSE;
        }
    }
 
    // Use the indices to create grid positions.
    vector GridPos1 = GetGridPos( llList2Integer(Indices, 0), llList2Integer(Indices, 1) );
    vector GridPos2 = GetGridPos( llList2Integer(Indices, 2), llList2Integer(Indices, 3) );
    vector GridPos3 = GetGridPos( llList2Integer(Indices, 4), llList2Integer(Indices, 5) );
    vector GridPos4 = GetGridPos( llList2Integer(Indices, 6), llList2Integer(Indices, 7) );
    vector GridPos5 = GetGridPos( llList2Integer(Indices, 8), llList2Integer(Indices, 9) );
    vector GridPos6 = GetGridPos( llList2Integer(Indices, 10), llList2Integer(Indices, 11) );
    vector GridPos7 = GetGridPos( llList2Integer(Indices, 12), llList2Integer(Indices, 13) );
    vector GridPos8 = GetGridPos( llList2Integer(Indices, 14), llList2Integer(Indices, 15) );
   
    // Use these grid positions to display the correct textures/offsets.
    ShowChars(link,GridPos1, GridPos2, GridPos3, GridPos4, GridPos5, GridPos6, GridPos7, GridPos8);
}
 
integer ConvertIndex(integer index) {
    // This converts from an ASCII based index to our indexing scheme.
    if (index >= 32) // ' ' or higher
        index -= 32;
    else { // index < 32
        // Quick bounds check.
        if (index > 15)
            index = 15;
 
        index += 94; // extended characters
    }
 
    return index;
}
 
 
default {
    state_entry()
    {
        // Initialize the character index.
        ResetCharIndex();
        llMessageLinked(LINK_THIS, REGISTER_SLAVE, llGetScriptName() , NULL_KEY); 
    }
 
  on_rez(integer num)
  {
      llResetScript();     
  }
 
    link_message(integer sender, integer channel, string data, key id) {
        if (channel == SLAVE_RECOGNIZED)
        {
            if (data == llGetScriptName())
            {
                gActive=TRUE;
            }
            return;
        }
   
        else if (channel == SLAVE_DISPLAY)
        {
            if (!gActive) 
                return;
               
            list params=llCSV2List((string)id);
            if (llList2String(params, 1) != llGetScriptName())
                return;
           
           
            RenderString(llList2Integer(params, 0),data);
            return;
        }
   
      else if (channel == SLAVE_DISPLAY_EXTENDED)
      {
            if (!gActive) 
                return;
               
            list params=llCSV2List((string)id);
            if (llList2String(params, 1) != llGetScriptName())
                return;
               
            RenderExtended(llList2Integer(params, 0),data);
    }
        else if (channel == REMAP_INDICES) {
            // Parse the message, splitting it up into index values.
            list Parsed = llCSV2List(data);
            integer i;
            // Go through the list and swap each pair of indices.
            for (i = 0; i < llGetListLength(Parsed); i += 2) {
                integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
                integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );
 
                // Swap these index values.
                string Value1 = llGetSubString(gCharIndex, Index1, Index1);
                string Value2 = llGetSubString(gCharIndex, Index2, Index2);
 
                gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
                gCharIndex = llInsertString(gCharIndex, Index1, Value2);
 
                gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
                gCharIndex = llInsertString(gCharIndex, Index2, Value1);
            }
            return;
        }
        else if (channel == RESET_INDICES) {
            // Restore the character index back to default settings.
            ResetCharIndex();
            return;
        }               
      else if (channel == SLAVE_RESET)
        {
            ResetCharIndex();
            gActive=FALSE;
            llMessageLinked(LINK_THIS, REGISTER_SLAVE, llGetScriptName() , NULL_KEY);
        }
    }   
}
</source>
 
== 16-Char MESH Prim Setup ==
<source lang="lsl2">
//There really isn't a Prim Setup for these
//You can get a full perm 8 face plane mesh here for L$10
//https://marketplace.secondlife.com/p/Mesh-8-Faces-8x1/3014457 [1]
// Or for completely FREE here
//https://marketplace.secondlife.com/p/FREE-8-faced-mesh-prim-xytext-v2-16-characters-per-half-a-prim/3312172 [2]
</source>
 
8x1, high LOD, land impact 0.5: [https://marketplace.secondlife.com/p/Free-full-perm-8-face-mesh-panel/4172861 https://marketplace.secondlife.com/p/Free-full-perm-8-face-mesh-panel/4172861 ]
 
[https://marketplace.secondlife.com/p/Mesh-8-Faces-8x1/3014457 https://marketplace.secondlife.com/p/Mesh-8-Faces-8x1/3014457]
 
[https://marketplace.secondlife.com/p/FREE-8-faced-mesh-prim-xytext-v2-16-characters-per-half-a-prim/3312172 https://marketplace.secondlife.com/p/FREE-8-faced-mesh-prim-xytext-v2-16-characters-per-half-a-prim/3312172]
{{LSLC|Library}}


{{LSLC|Library}}{{LSLC|Examples}}
{{#vardefine:sort|XyzzyText 2.1}}

Latest revision as of 09:32, 25 January 2015

Scripting tools to allow display of text on a prim: XyText 1.5 , XyzzyText, XyyyyzText, XyText-UTF8, XyzzyText-UTF8, ZZText, VariText


XyzzyText is a new version of XyText 1.5 with some advanced features:

  • Efficiency! You can now control an entire linkset with just one script.
  • Flexible Speed vs Lag. Just drop in plug-and-play slave scripts to increase rendering speed. (More than 4-5 slaves not recommended, there is diminishing returns)
  • Ease of use. There is no need to know link numbers. Simply name the child prims xyzzytext-banknumber-cellnumber.
  • To use, pass the bank number to use in the "key" field of the link message of DISPLAY_STRING/EXTENDED.

Copyright & Licensing

  • Traven Sachs hereby releases his contributions to this under the MIT license (16 Char MESH Prim Mod).
  • Gigs Taggart hereby releases his contributions to this under the MIT license.
  • Thraxis Epsilon hereby releases his contributions to this under the MIT license.
  • Strife Onizuka hereby releases his contributions to this under the MIT license.
  • Huney Jewell hereby releases her contributions to this under the MIT license.
  • Salahzar Stenvaag hereby releases his contributions to this under the MIT license. (photoshop/GIMP vbs script for alternative fonts plus ZZText)
  • Ruud Lathrop hereby releases her contributions to this under the MIT license.
  • Joel Cloquet hereby releases his contributions to this under the MIT license.

XyzzyText - 5 Character

Example:

Display Prims named: 
xyzzytext-0-0 xyzzytext-0-1
xyzzytext-1-0 xyzzytext-1-1

Quickstart:

* Store all scripts in your inventory
* Rez a box
* Drop the prim setup script in it (You now have one unit, name it xyzzytext-0-0)
* Make a row of four of these prims (xyzzytext-0-0 xyzzytext-0-1,xyzzytext-0-2,xyzzytext-0-3)
* Move a copy of this row a little down, and rename the new row(xyzzytext-1-0 xyzzytext-1-1,xyzzytext-1-2,xyzzytext-1-3)
* Do this for every row you make
* Create a big box that serves as the board-background(mainprim)
* Link all prims with the mainprim(selected last), so the mainprim keeps yellow after linking(root-prim)
* Copy the main script and usage script into the root prim
* Type something, and check if it all works
* Flatten the prims, so the text looks straight
* Consider adding slave scripts to the root prim if you have a lot of rows

Usage:

integer DISPLAY_STRING      = 204000; 
integer DISPLAY_EXTENDED    = 204001; 
integer REMAP_INDICES       = 204002; 
integer RESET_INDICES       = 204003; 
integer SET_FADE_OPTIONS    = 204004; 
integer SET_FONT_TEXTURE    = 204005; 
integer SET_LINE_COLOR      = 204006; 
integer SET_COLOR           = 204007; 
integer RESCAN_LINKSET      = 204008;


integer gToggle;

default
{
    state_entry()
    {
        llListen(0,"",NULL_KEY,"");
       
    }

    
    listen(integer channel,string name, key id, string message)
    {
        if (gToggle)
            llMessageLinked(LINK_THIS,DISPLAY_STRING,name + ":"+ message,"0");
        else
            llMessageLinked(LINK_THIS,DISPLAY_STRING,name + ":"+ message,"1");
        
        gToggle=!gToggle;

    }
}

Main Script

//////////////////////////////////////////// 
// XyzzyText v2.1 Script (Set Line Color) by Huney Jewell
// XyzzyText v2.0 Script (5 Face, Single Texture) 
//
//
// Edited trivially by Joel Cloquet on 1/2011 to use the (relatively)
// new llSetLinkPrimitiveParamsFast function, thereby removing the need in
// some cases to use the slave script.
//
// Heavily Modified by Thraxis Epsilon, Gigs Taggart 5/2007 and Strife Onizuka 8/2007
// Rewrite to allow one-script-per-object operation w/ optional slaves
// Enable prim-label functionality
// Enabled Banking
//
// Modified by Kermitt Quirk 19/01/2006 
// To add support for 5 face prim instead of 3 
// 
// Core XyText Originally Written by Xylor Baysklef 
// 
//
//////////////////////////////////////////// 

/////////////// CONSTANTS /////////////////// 
// XyText Message Map. 
integer DISPLAY_STRING      = 204000; 
integer DISPLAY_EXTENDED    = 204001; 
integer REMAP_INDICES       = 204002; 
integer RESET_INDICES       = 204003; 
integer SET_FADE_OPTIONS    = 204004; 
integer SET_FONT_TEXTURE    = 204005; 
integer SET_LINE_COLOR      = 204006; 
integer SET_COLOR           = 204007; 
integer RESCAN_LINKSET      = 204008;




//internal API
integer REGISTER_SLAVE      = 205000;
integer SLAVE_RECOGNIZED    = 205001;
integer SLAVE_DISPLAY       = 205003;
integer SLAVE_DISPLAY_EXTENDED = 205004;
integer SLAVE_RESET = 205005;

// This is an extended character escape sequence. 
string  ESCAPE_SEQUENCE = "\\e"; 

// This is used to get an index for the extended character. 
string  EXTENDED_INDEX  = "12345"; 

// Face numbers. 
integer FACE_1          = 3; 
integer FACE_2          = 7; 
integer FACE_3          = 4; 
integer FACE_4          = 6; 
integer FACE_5          = 1; 

// Used to hide the text after a fade-out. 
key     TRANSPARENT     = "701917a8-d614-471f-13dd-5f4644e36e3c";
key     null_key        = NULL_KEY;
///////////// END CONSTANTS //////////////// 

///////////// GLOBAL VARIABLES /////////////// 
// This is the key of the font we are displaying. 
key     gFontTexture        = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e"; 
// All displayable characters.  Default to ASCII order. 
string gCharIndex; 

// This is whether or not to use the fade in/out special effect. 
integer gCellUseFading      = FALSE; 
// This is how long to display the text before fading out (if using 
// fading special effect). 
// Note: < 0  means don't fade out. 
float   gCellHoldDelay      = 1.0; 

integer gSlaveRegistered;
list gSlaveNames;

integer BANK_STRIDE=3; //offset, length, highest_dirty
list gBankingData;

/////////// END GLOBAL VARIABLES //////////// 

ResetCharIndex() { 
   gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"; 
   gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~"; 
   gCharIndex += "\n\n\n\n\n"; 
} 

vector GetGridOffset(integer index) { 
   // Calculate the offset needed to display this character. 
   integer Row = index / 10; 
   integer Col = index % 10; 

   // Return the offset in the texture. 
   return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>; 
} 

ShowChars(integer link,vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) { 
   // Set the primitive textures directly. 
    
   // <-0.256, 0, 0> 
   // <0, 0, 0> 
   // <0.130, 0, 0> 
   // <0, 0, 0> 
   // <-0.74, 0, 0> 
    
   llSetLinkPrimitiveParamsFast( link,[ 
        PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.126, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0, 
        PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0, 
        PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0, 
        PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0, 
        PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.126, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0 
        ]); 
} 

RenderString(integer link, string str) { 
   // Get the grid positions for each pair of characters. 
   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) ); 
   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) ); 
   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) ); 
   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) ); 
   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) ); 

   // Use these grid positions to display the correct textures/offsets. 
   ShowChars(link,GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5); 
} 

RenderWithEffects(integer link, string str) { 
   // Get the grid positions for each pair of characters. 
   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) ); 
   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) ); 
   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) ); 
   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) ); 
   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) ); 

   // First set the alpha to the lowest possible. 
   llSetLinkAlpha(link,0.05, ALL_SIDES); 

   // Use these grid positions to display the correct textures/offsets. 
   ShowChars(link,GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);          // Now turn up the alpha until it is at full strength. 
    float Alpha = 0.10; 
    for (; Alpha <= 1.0; Alpha += 0.05) 
       llSetLinkAlpha(link,Alpha, ALL_SIDES); 
          // See if we want to fade out as well. 
   if (gCellHoldDelay < 0.0) 
       // No, bail out. (Just keep showing the string at full strength). 
       return; 
          // Hold the text for a while. 
   llSleep(gCellHoldDelay); 
      // Now fade out. 
   for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05) 
       llSetLinkAlpha(link,Alpha, ALL_SIDES); 
          // Make the text transparent to fully hide it. 
   llSetLinkTexture(link,TRANSPARENT, ALL_SIDES); 
} 

integer RenderExtended(integer link, string str, integer render) {
   // Look for escape sequences. 
   integer length = 0;
   list Parsed       = llParseString2List(str, [], (list)ESCAPE_SEQUENCE); 
   integer ParsedLen = llGetListLength(Parsed); 

   // Create a list of index values to work with. 
   list Indices; 
   // We start with room for 5 indices. 
   integer IndicesLeft = 5; 

   string Token; 
   integer Clipped; 
   integer LastWasEscapeSequence = FALSE; 
   // Work from left to right. 
   integer i = 0;
   for (; i < ParsedLen && IndicesLeft > 0; ++i) { 
       Token = llList2String(Parsed, i); 

       // If this is an escape sequence, just set the flag and move on. 
       if (Token == ESCAPE_SEQUENCE) { 
           LastWasEscapeSequence = TRUE; 
       } 
       else { // Token != ESCAPE_SEQUENCE 
           // Otherwise this is a normal token.  Check its length. 
           Clipped = FALSE; 
           integer TokenLength = llStringLength(Token); 
           // Clip if necessary. 
           if (TokenLength > IndicesLeft) { 
               TokenLength = llStringLength(Token = llGetSubString(Token, 0, IndicesLeft - 1)); 
               IndicesLeft = 0; 
               Clipped = TRUE; 
           } 
           else 
               IndicesLeft -= TokenLength; 

           // Was the previous token an escape sequence? 
           if (LastWasEscapeSequence) { 
               // Yes, the first character is an escape character, the rest are normal. 
               length += 2 + TokenLength;
               if(render)
               {
                    // This is the extended character. 
                    Indices += (llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95);
                    
                    // These are the normal characters.
                    integer j = 1; 
                    for (; j < TokenLength; ++j) 
                    {
                       Indices += llSubStringIndex(gCharIndex, llGetSubString(Token, j, j)); 
                    }
                }
           } 
           else { // Normal string. 
               // Just add the characters normally. 
               length += TokenLength;
               if(render)
               {               
                   integer j = 0; 
                   for (; j < TokenLength; ++j) 
                   {                  
                       Indices += llSubStringIndex(gCharIndex, llGetSubString(Token, j, j));
                   }
                }
           } 

           // Unset this flag, since this was not an escape sequence. 
           LastWasEscapeSequence = FALSE; 
       } 
   } 

   // Use the indices to create grid positions.
  if(render)
  {   
       vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0) ); 
       vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) ); 
       vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) ); 
       vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) ); 
       vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) ); 
    
       // Use these grid positions to display the correct textures/offsets. 
       ShowChars(link,GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5);
   }
   return length;
} 

integer ConvertIndex(integer index) { 
   // This converts from an ASCII based index to our indexing scheme. 
   if (index >= 32) // ' ' or higher 
       index -= 32; 
   else { // index < 32 
       // Quick bounds check. 
       if (index > 15) 
           index = 15; 

       index += 94; // extended characters 
   } 

   return index; 
} 



PassToRender(integer render,string message, integer bank)
{
//    float time;
    integer extendedlen = 0;
    integer link;
    integer i = 0;
    integer msgLen = llStringLength(message);
    string TextToRender;
    integer num_slaves=llGetListLength(gSlaveNames);
    string slave_name; //avoids unnecessary casts, keeping it as a string
    

    //get the bank offset and length
    integer bank_offset=llList2Integer(gBankingData, (bank * BANK_STRIDE));
    integer bank_length=llList2Integer(gBankingData, (bank * BANK_STRIDE) + 1);
    integer bank_highest_dirty=llList2Integer(gBankingData, (bank * BANK_STRIDE) + 2);
    
    integer x = 0;
    for (;x < msgLen;x = x + 5)
    {

        if (i >= bank_length)  //we don't want to run off the end of the bank
        {
            //set the dirty to max, and bail out, we're done
            gBankingData=llListReplaceList(gBankingData, (list)bank_length, (bank * BANK_STRIDE) + 2, (bank * BANK_STRIDE) + 2);
            return;
        }
        
        link = unpack(gXyTextPrims,(i + bank_offset));
        TextToRender = llGetSubString(message, x, x + 15);
        
        if(gSlaveRegistered && (link % (num_slaves + 1)))
        {
            slave_name=llList2String(gSlaveNames, (link % (num_slaves + 1)) - 1);
            if (render == 1)
               llMessageLinked(LINK_THIS, SLAVE_DISPLAY, TextToRender, (key)((string)link + "," + slave_name));
            if (render == 2)
            {
                //time = llGetAndResetTime();
                if(llSubStringIndex(TextToRender,"\e") > x)
                    extendedlen = 5;
                else
                    extendedlen = RenderExtended(link,TextToRender,0);
                if (extendedlen > 5)
                {
                    x += extendedlen - 5;
                }
               llMessageLinked(LINK_THIS, SLAVE_DISPLAY_EXTENDED, TextToRender, (key)((string)link + "," + slave_name));
              // llOwnerSay((string)llGetAndResetTime());
            }
               //sorry, no fade effect with slave
        }
        else
        {
            if (render == 1)
                RenderString(link,TextToRender);
            if (render == 2)
            {
                extendedlen = RenderExtended(link,TextToRender,1);
                if (extendedlen > 5)
                {
                    x += extendedlen - 5;
                }
                
            }
            if (render == 3)
                RenderWithEffects(link,TextToRender);
        }
        ++i;            
    }
    
    if (bank_highest_dirty==0)
        bank_highest_dirty=bank_length;
    
    integer current_highest_dirty=i;
    while (i < bank_highest_dirty)
    {
        link = unpack(gXyTextPrims,(i + bank_offset));
        
        if(gSlaveRegistered && (link % (num_slaves+1) != 0))
        {
            slave_name=llList2String(gSlaveNames, (link % (num_slaves + 1)) - 1);
            llMessageLinked(LINK_THIS, SLAVE_DISPLAY, "     ", (key)((string)link + "," + slave_name));       
            //sorry, no fade effect with slave
        }
        else
        {
            RenderString(link,"     ");
        }
        ++i;        
    }
    gBankingData=llListReplaceList(gBankingData, (list)current_highest_dirty, (bank * BANK_STRIDE) + 2, (bank * BANK_STRIDE) + 2);
}

// Bitwise Voodoo by Gigs Taggart and optimized by Strife Onizuka
list gXyTextPrims;


integer get_number_of_prims()
{//ignores avatars.
    integer a = llGetNumberOfPrims();
    //Mono tweak
    vector size = llGetAgentSize(llGetLinkKey(a));
    while(size.z > 0)
    {
        --a;
        size = llGetAgentSize(llGetLinkKey(a));
    }
    return a;
}

//functions to pack 8-bit shorts into ints
list pack_and_insert(list in_list, integer pos, integer value)
{
//    //figure out the bitpack position
//    integer pack = pos & 3; //4 bytes per int
//    pos=pos >> 2;
//    integer shifted = value << (pack << 3);
//    integer old_value = llList2Integer(in_list, pos);
//    shifted = old_value | shifted;
//    in_list = llListReplaceList(in_list, (list)shifted, pos, pos);
//    return in_list;
    //Safe optimized version
    integer index = pos >> 2;
    return llListReplaceList(in_list, (list)(llList2Integer(in_list, index) | (value << ((pos & 3) << 3))), index, index);
}

integer unpack(list in_list, integer pos)
{
    return (llList2Integer(in_list, pos >> 2) >> ((pos & 3) << 3)) & 0x000000FF;//unsigned
//    return (llList2Integer(in_list, pos >> 2) << (((~pos) & 3) << 3)) >> 24;//signed
}

change_color(vector color)
{
    integer num_prims=llGetListLength(gXyTextPrims) << 2;
    
    integer i = 0;
    
    for (; i<=num_prims; ++i)
    {
        integer link = unpack(gXyTextPrims,i);
        if (!link)
            return;
        
        llSetLinkPrimitiveParamsFast( link,[ 
            PRIM_COLOR, FACE_1, color, 1.0,
            PRIM_COLOR, FACE_2, color, 1.0,
            PRIM_COLOR, FACE_3, color, 1.0,
            PRIM_COLOR, FACE_4, color, 1.0,
            PRIM_COLOR, FACE_5, color, 1.0
        ]);
    }
}

change_line_color(integer bank, vector color)
{    

    //get the bank offset and length
    integer i = llList2Integer(gBankingData, (bank * BANK_STRIDE));
    integer bank_end = i + llList2Integer(gBankingData, (bank * BANK_STRIDE) + 1);

    for (; i < bank_end; ++i)
    {     
        integer link = unpack(gXyTextPrims,i);
        if (!link)
            return;
        
        llSetLinkPrimitiveParamsFast( link,[ 
            PRIM_COLOR, FACE_1, color, 1.0,
            PRIM_COLOR, FACE_2, color, 1.0,
            PRIM_COLOR, FACE_3, color, 1.0,
            PRIM_COLOR, FACE_4, color, 1.0,
            PRIM_COLOR, FACE_5, color, 1.0
        ]);
    }
}


init()
{
    integer num_prims=get_number_of_prims();
    string link_name;
    integer bank=0;
    integer prims_pointer=0; //"pointer" to the next entry to be used in the gXyTextPrims list.
    
    list temp_bank = [];
    integer temp_bank_stride=2;
    
    //FIXME: font texture might should be per-bank
    llMessageLinked(LINK_THIS, SET_FONT_TEXTURE, "", gFontTexture);
    
    // moving this before the prim scan so that the slaves properly configure themseves before
    // any requests to display
    llMessageLinked(LINK_THIS, SLAVE_RESET, "" , null_key);
 
    gXyTextPrims=[];
    integer x=0;
    for (;x<64;++x)
    {
        gXyTextPrims = (gXyTextPrims = []) + gXyTextPrims + 0;  //we need to pad out the list to make it easier to add things in any order later
    }
    
    gBankingData = [];
    @loop;
    {
        //loop over all prims, looking for ones in the current bank
        for(x=0;x<=num_prims;++x)
        {
            link_name=llGetLinkName(x);
            list tmp = llParseString2List(link_name, (list)"-", []);
            if(llList2String(tmp,0) == "xyzzytext")
            {
                if (llList2Integer(tmp,1) == bank)
                {
                    temp_bank += llList2Integer(tmp,2) + (list)x;
                }
            }
            
        }

        if (temp_bank != [])
        {
            //sort the current bank
            temp_bank = llListSort(temp_bank, temp_bank_stride, TRUE);
            
            integer temp_len = llGetListLength(temp_bank);
            
            //store metadata
            gBankingData += [prims_pointer, temp_len/temp_bank_stride, 0];
            
            //repack the bank into the prim list
            for (x = 0; x < temp_len; x += temp_bank_stride)
            {
                gXyTextPrims = pack_and_insert(gXyTextPrims, prims_pointer, llList2Integer(temp_bank, x + 1));
                ++prims_pointer;
            }
            ++bank;
            temp_bank=[];
            jump loop;
        }
    }
    
    //llOwnerSay((string)llGetFreeMemory());
}


default { 
   state_entry() { 
       // Initialize the character index. 
       ResetCharIndex();
       init();
   } 

   on_rez(integer num)
   {
      llResetScript();       
   }
   
   link_message(integer sender, integer channel, string data, key id) { 
        if (id==null_key)
            id="0";
            
        if (channel == DISPLAY_STRING) { 
            PassToRender(1,data, (integer)((string)id)); 
        } 
        else if (channel == DISPLAY_EXTENDED) { 
            PassToRender(2,data, (integer)((string)id)); 
        } 
        else if (channel == REMAP_INDICES) { 
            // Parse the message, splitting it up into index values. 
            list Parsed = llCSV2List(data); 
            integer i = 0; 
            // Go through the list and swap each pair of indices. 
            for (; i < llGetListLength(Parsed); i += 2) { 
                integer Index1 = ConvertIndex( llList2Integer(Parsed, i) ); 
                integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) ); 
        
                // Swap these index values. 
                string Value1 = llGetSubString(gCharIndex, Index1, Index1); 
                string Value2 = llGetSubString(gCharIndex, Index2, Index2); 
        
                gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1); 
                gCharIndex = llInsertString(gCharIndex, Index1, Value2);
        
                gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
                gCharIndex = llInsertString(gCharIndex, Index2, Value1);
            } 
        } 
        else if (channel == RESCAN_LINKSET)
        {
            init();
        }
        else if (channel == RESET_INDICES) { 
            // Restore the character index back to default settings. 
            ResetCharIndex(); 
        } 
        else if (channel == SET_FADE_OPTIONS) { 
            // Change the channel we listen to for cell commands, the 
            // starting character position to extract from, and 
            // special effect attributes. 
            list Parsed = llCSV2List(data); 
            gCellUseFading      = (integer) llList2String(Parsed, 0); 
            gCellHoldDelay      = (float)   llList2String(Parsed, 1);
        } 
        else if (channel == SET_FONT_TEXTURE) { 
            // Use the new texture instead of the current one. 
            gFontTexture = id; 
        } 
        else if (channel == SET_COLOR) {
            change_color((vector)data); 
        }
        else if (channel == SET_LINE_COLOR) {
            change_line_color((integer)((string)id), (vector)data); 
        } 
        else if (channel == REGISTER_SLAVE)
        {
            if(!~llListFindList(gSlaveNames, (list)data))
            {//isn't registered yet
                gSlaveNames += data;
                gSlaveRegistered=TRUE;
                //llOwnerSay((string)llGetListLength(gSlaveNames) + " Slave(s) Recognized: " + data);
            }
//            else
//            {//it already exists
//                llOwnerSay((string)llGetListLength(gSlaveNames) + " Slave, Existing Slave Recognized: " + data);
//            }
            llMessageLinked(LINK_THIS, SLAVE_RECOGNIZED, data , null_key);
        }
    } 
   
   
    changed(integer change)
    {
        if(change&CHANGED_INVENTORY)
        {
            if(gSlaveRegistered)        
            {
                //by using negative indexes they don't need to be adjusted when an entry is deleted.
                integer x = ~llGetListLength(gSlaveNames);
                while(++x)
                {
                    if (!~llGetInventoryType(llList2String(gSlaveNames, x)))
                    {
                        //llOwnerSay("Slave Removed: " + llList2String(gSlaveNames, x));
                        gSlaveNames = llDeleteSubList(gSlaveNames, x, x);
                    }
                }
                gSlaveRegistered = !(gSlaveNames == []);
            }
        }
    }
}

Slave Script

//////////////////////////////////////////// 
// XyText v2.0 SLAVE Script (5 Face, Single Texture) 
//
// Modified by Thraxis Epsilon and Gigs Taggart 5/2007
// Rewrite to allow one-script-per-object operation
//
// Modified by Kermitt Quirk 19/01/2006 
// To add support for 5 face prim instead of 3 
// 
// Originally Written by Xylor Baysklef 
// 
//
//////////////////////////////////////////// 


integer REMAP_INDICES       = 204002; 
integer RESET_INDICES       = 204003;

//internal API
integer REGISTER_SLAVE      = 205000;
integer SLAVE_RECOGNIZED    = 205001;
integer SLAVE_DISPLAY       = 205003;
integer SET_FONT_TEXTURE    = 204005; 

integer SLAVE_DISPLAY_EXTENDED = 205004;
integer SLAVE_RESET = 205005;


// This is an extended character escape sequence. 
string  ESCAPE_SEQUENCE = "\\e"; 

// This is used to get an index for the extended character. 
string  EXTENDED_INDEX  = "12345"; 

// Face numbers. 
integer FACE_1          = 3; 
integer FACE_2          = 7; 
integer FACE_3          = 4; 
integer FACE_4          = 6; 
integer FACE_5          = 1; 


///////////// GLOBAL VARIABLES /////////////// 
// This is the key of the font we are displaying. 
key     gFontTexture        = "b2e7394f-5e54-aa12-6e1c-ef327b6bed9e"; 
// All displayable characters.  Default to ASCII order. 
string gCharIndex; 

integer gActive; //if we are recognized, this is true
/////////// END GLOBAL VARIABLES //////////// 

ResetCharIndex() { 
   gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"; 
   gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~"; 
   gCharIndex += "\n\n\n\n\n"; 
} 

vector GetGridOffset(integer index) { 
   // Calculate the offset needed to display this character. 
   integer Row = index / 10; 
   integer Col = index % 10; 

   // Return the offset in the texture. 
   return <-0.45 + 0.1 * Col, 0.45 - 0.1 * Row, 0.0>; 
} 

ShowChars(integer link,vector grid_offset1, vector grid_offset2, vector grid_offset3, vector grid_offset4, vector grid_offset5) { 
   // Set the primitive textures directly. 
    
   // <-0.256, 0, 0> 
   // <0, 0, 0> 
   // <0.130, 0, 0> 
   // <0, 0, 0> 
   // <-0.74, 0, 0> 
    
   llSetLinkPrimitiveParamsFast( link,[ 
        PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.126, 0.1, 0>, grid_offset1 + <0.037, 0, 0>, 0.0, 
        PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset2, 0.0, 
        PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.1, 0>, grid_offset3 - <0.244, 0, 0>, 0.0, 
        PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.1, 0>, grid_offset4, 0.0, 
        PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.126, 0.1, 0>, grid_offset5 - <0.037, 0, 0>, 0.0 
        ]); 
} 

RenderString(integer link, string str) {
   // Get the grid positions for each pair of characters. 
   vector GridOffset1 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)) ); 
   vector GridOffset2 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) ); 
   vector GridOffset3 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)) ); 
   vector GridOffset4 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) ); 
   vector GridOffset5 = GetGridOffset( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)) ); 

   // Use these grid positions to display the correct textures/offsets. 
   ShowChars(link,GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5); 
} 

RenderExtended(integer link, string str) { 
   // Look for escape sequences. 
   list Parsed       = llParseString2List(str, [], [ESCAPE_SEQUENCE]); 
   integer ParsedLen = llGetListLength(Parsed); 

   // Create a list of index values to work with. 
   list Indices; 
   // We start with room for 5 indices. 
   integer IndicesLeft = 5; 

   integer i = 0; 
   string Token; 
   integer Clipped; 
   integer LastWasEscapeSequence = FALSE; 
   // Work from left to right. 
   for (; i < ParsedLen && IndicesLeft > 0; ++i) { 
       Token = llList2String(Parsed, i); 

       // If this is an escape sequence, just set the flag and move on. 
       if (Token == ESCAPE_SEQUENCE) { 
           LastWasEscapeSequence = TRUE; 
       } 
       else { // Token != ESCAPE_SEQUENCE 
           // Otherwise this is a normal token.  Check its length. 
           Clipped = FALSE; 
           integer TokenLength = llStringLength(Token); 
           // Clip if necessary. 
           if (TokenLength > IndicesLeft) { 
               Token = llGetSubString(Token, 0, IndicesLeft - 1); 
               TokenLength = llStringLength(Token); 
               IndicesLeft = 0; 
               Clipped = TRUE; 
           } 
           else 
               IndicesLeft -= TokenLength; 

           // Was the previous token an escape sequence? 
           if (LastWasEscapeSequence) { 
               // Yes, the first character is an escape character, the rest are normal. 

               // This is the extended character. 
               Indices += (llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95); 

               // These are the normal characters. 
               integer j = 1; 
               for (; j < TokenLength; ++j) 
                   Indices += llSubStringIndex(gCharIndex, llGetSubString(Token, j, j)); 
           } 
           else { // Normal string. 
               // Just add the characters normally. 
               integer j; 
               for (j = 0; j < TokenLength; ++j) 
                   Indices += llSubStringIndex(gCharIndex, llGetSubString(Token, j, j)); 
           } 

           // Unset this flag, since this was not an escape sequence. 
           LastWasEscapeSequence = FALSE; 
       } 
   } 

   // Use the indices to create grid positions. 
   vector GridOffset1 = GetGridOffset( llList2Integer(Indices, 0) ); 
   vector GridOffset2 = GetGridOffset( llList2Integer(Indices, 1) ); 
   vector GridOffset3 = GetGridOffset( llList2Integer(Indices, 2) ); 
   vector GridOffset4 = GetGridOffset( llList2Integer(Indices, 3) ); 
   vector GridOffset5 = GetGridOffset( llList2Integer(Indices, 4) ); 

   // Use these grid positions to display the correct textures/offsets. 
   ShowChars(link,GridOffset1, GridOffset2, GridOffset3, GridOffset4, GridOffset5); 
} 

integer ConvertIndex(integer index) { 
   // This converts from an ASCII based index to our indexing scheme. 
   if (index >= 32) // ' ' or higher 
       index -= 32; 
   else { // index < 32 
       // Quick bounds check. 
       if (index > 15) 
           index = 15; 

       index += 94; // extended characters 
   } 

   return index; 
} 


default 
{ 
    state_entry() 
    { 
        // Initialize the character index. 
        ResetCharIndex();
        llMessageLinked(LINK_THIS, REGISTER_SLAVE, llGetScriptName() , NULL_KEY);  
    } 
    
    on_rez(integer num)
    {
        llResetScript();       
    }
    
    link_message(integer sender, integer channel, string data, key id) 
    { 
    
        if (channel == SLAVE_RECOGNIZED)
        {
            if (data == llGetScriptName())
            {
                gActive=TRUE;
            }
            return;
        }
    
        if (channel == SLAVE_DISPLAY) 
        { 
            if (!gActive)  
                return;
                
            list params=llCSV2List((string)id);
            if (llList2String(params, 1) != llGetScriptName())
                return;
            
            
            RenderString(llList2Integer(params, 0),data);
            return; 
        } 
       
       if (channel == SLAVE_DISPLAY_EXTENDED) 
       {
            if (!gActive)  
                return;
                
            list params=llCSV2List((string)id);
            if (llList2String(params, 1) != llGetScriptName())
                return;
                
            RenderExtended(llList2Integer(params, 0),data);
       } 
       
        if (channel == SET_FONT_TEXTURE) 
        { 
           gFontTexture = id; 
           return; 
        } 
       
        if (channel == REMAP_INDICES) { 
           // Parse the message, splitting it up into index values. 
           list Parsed = llCSV2List(data); 
           integer i; 
           // Go through the list and swap each pair of indices. 
           for (i = 0; i < llGetListLength(Parsed); i += 2) { 
               integer Index1 = ConvertIndex( llList2Integer(Parsed, i) ); 
               integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) ); 
        
               // Swap these index values. 
               string Value1 = llGetSubString(gCharIndex, Index1, Index1); 
               string Value2 = llGetSubString(gCharIndex, Index2, Index2); 
        
               gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1); 
               gCharIndex = llInsertString(gCharIndex, Index1, Value2); 
        
               gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2); 
               gCharIndex = llInsertString(gCharIndex, Index2, Value1); 
           } 
           return; 
        } 
        if (channel == RESET_INDICES) { 
           // Restore the character index back to default settings. 
           ResetCharIndex(); 
           return; 
        } 

        
        if (channel == SLAVE_RESET)
        {
            ResetCharIndex();
            gActive=FALSE;
            llMessageLinked(LINK_THIS, REGISTER_SLAVE, llGetScriptName() , NULL_KEY);
        }
    
    } 
    
   
}

Prim Setup

(Caution! Not compatible with 5 face double texture Xytext)

////////////////////////////////////////////
// XyzzyText Prim Setup Script (5 Face)
//
// Modified by Thraxis Epsilon
//
////////////////////////////////////////////

default
{
    state_entry()
    {

        llSetPrimitiveParams([
                    PRIM_TYPE, PRIM_TYPE_PRISM, PRIM_HOLE_DEFAULT, <0.199, 0.8, 0.0>, 0.68, <0.0, 0.0, 0.0>, <1.0, 1.0, 0.0>, <0.0, 0.0, 0.0>, 
                    PRIM_SIZE, <0.03, 2.89, 0.5>, 
                    PRIM_TEXTURE, 1, "09b04244-9569-d21f-6de0-4bbcf5552222", <0.126, 0.10, 0.0>, <-0.740013, 0.0, 0.0>, 0.0, 
                    PRIM_TEXTURE, 6, "09b04244-9569-d21f-6de0-4bbcf5552222", <0.050, 0.100, 0.00>, <0.0, 0.0, 0.0>, 0.0, 
                    PRIM_TEXTURE, 4, "09b04244-9569-d21f-6de0-4bbcf5552222", <-0.740, 0.10, 0.00>, <0.130009, 0.0, 0.0>, 0.0, 
                    PRIM_TEXTURE, 7, "09b04244-9569-d21f-6de0-4bbcf5552222", <0.050, 0.100, 0.00>, <0.0, 0.0, 0.0>, 0.0, 
                    PRIM_TEXTURE, 3, "09b04244-9569-d21f-6de0-4bbcf5552222", <0.126, 0.10, 0.0>, <-0.255989, 0.0, 0.0>, 0.0]);
                    
        llRemoveInventory(llGetScriptName());
    } 
}


How to produce textures with alternative fonts for 5 character version (Photoshop VBscript version)

Note: If you need to use XyzzyText for European or UTF-8 other languages, please see the following link: XyzzyText-UTF8 Use the following section for GIMP script-fu if you prefer opensource alternative. Use the following link: ZZText for an alternative international solution (still based on xytext and xyzzytext) using even fewer textures and lag than the xytext solution.

If you need to display with another font, italics or other characters, you can do the following :)

Using Photoshop VB Scripting on windows, just open up Photoshop CS3, and create with notepad the following MakeFonts.vbs:

Dim appRef
Set appRef = CreateObject( "Photoshop.Application" )
' Remember current unit settings and then set units to
' the value expected by this script
Dim originalRulerUnits
originalRulerUnits = appRef.Preferences.RulerUnits
appRef.Preferences.RulerUnits = 1 ' psPixels
' Create a new 512*512 pixel document and assign it to a variable.
Dim docRef
Dim artLayerRef
Dim textItemRef
dim rows(10)
rows(1)=" !""#$%&'()"
rows(2)="*+,-./0123"
rows(3)="456789:;<="
rows(4)=">?@ABCDEFG"
rows(5)="HIJKLMNOPQ"
rows(6)="RSTUVWXYZ["
rows(7)="\]^_`abcde"
rows(8)="fghijklmno"
rows(9)="pqrstuvwxy"
rows(10)="z{|}~·ΘΩ√"      

dim fontname
fontname="Courier"

Set docRef = appRef.Documents.Add(512, 512, 72, "Table for font " & fontname, 2, 3, 1)
' Create a new art layer containing text
Dim colorObj
Set colorObj=CreateObject("Photoshop.SolidColor")
colorObj.RGB.Red=255
colorObj.RGB.Green=255
colorObj.RGB.Blue=255
' Set the contents of the text layer.
for i=1 to 10
   for j=1 to 10
     Set artLayerRef = docRef.ArtLayers.Add
     artLayerRef.Kind = 2
     Set textItemRef = artLayerRef.TextItem
     ' textItemRef.Font = fontname
     textItemRef.Color=colorObj
     textItemRef.Justification = 2 
     textItemRef.Font = "A Charming Font"
     textItemRef.Size = 38
     textItemRef.Contents = mid(rows(i),j,1)
     textItemRef.Position = Array((j-1)*51.2+25,i*51.2-7)
   next
next
' Restore unit settingPhotoshop CS3
appRef.Preferences.RulerUnits = originalRulerUnits

Just click on the file in internet explorer and it will start working with your photoshop (I'll take a bit to display all the 100 characters, maybe around 1 sec for character).

This will layout the 100 characters in photoshop on 100 different layers. Then do the following:

  • select all the layers with text and go to Text tool and change to whatever font (size etc) you want
  • maybe need to add a black layer to see your fonts :) (after you have to hid or delete this)
  • merge all the layers in just one
  • add an alpha channel for transparency (all black)
  • select all in the merged text layer and copy it to alpha channel
  • enable the alpha channel and the RGB channel so that you see your font set and the transparent red
  • save the font as TGA preserving alpha channel
  • upload it to SecondLife 10 L$
  • get the UUID from the inventory and specify it instead of the standard template.

How to produce textures with alternative fonts for 5 character version (GIMP script-fu version)

Use the following script for generating characters with GIMP:

To use it you must put under the $HOME/.gimpXXX/scripts directory and name it something.scm. Then you will find it under gimp <Toolbox>/Xtns/Script-Fu/Text. Clicking on it you are prompted for font to use (use monospaced fonts Andale Mono for instance). (Change only font and fontsize 50 should be ok, others are hard-coded in the script). Adapt with new unicode characters if you need them.

Note: If you need to use XyzzyText for European or UTF-8 other languages, please see the following link: XyzzyText-UTF8 Use the following section for GIMP script-fu if you prefer opensource alternative. Use the following link: ZZText for an alternative international solution (still based on xytext and xyzzytext) using even fewer textures and lag than the xytext solution.


(define (script-fu-xytext-simple inText inFont inFontSize inTextColor)
   (let*
       (
       ; define our local variables
       ; create a new image:
       (theImageWidth  512)
       (theImageHeight 512)
       (theImage)
       (theLayer )
       (x 0)
       (y 0)
       (p 32)
       (theText)
       (rows #(
               " !\"#$%&'()"
               "*+,-./0123"
               "456789:;<="
               ">?@ABCDEFG"
               "HIJKLMNOPQ"
               "RSTUVWXYZ["
               "\\]^_`abcde"
               "fghijklmno"
               "pqrstuvwxy"

               ;"z{|}~·thetaOmegaRadix.o "
               (list #\z #\{ #\| #\} #\~ #x387 #x3F4 #x3A9r #x221A #x387 )
              )
       )
       (el 0)
       (ch 0)
       ) ;end of our local variables

  ; builds image
  (set! theImage (car    (gimp-image-new theImageWidth theImageHeight RGB)))
  ; builds layer
  (set! theLayer (car (gimp-layer-new theImage theImageWidth theImageHeight RGB-IMAGE "layer 1" 100 NORMAL) ) )
  ; add layer to image
  (gimp-image-add-layer theImage theLayer 0)

  ; add alpha and set foreground
  (gimp-layer-add-alpha theLayer)
  (gimp-palette-set-foreground inTextColor)

  ; clear all the image and remove selection
  (gimp-selection-all theImage)
  (gimp-edit-clear theLayer)
  (gimp-selection-none theImage)

  ; do actual plot of characters
  (while (< x 10)
    (while (< y 10)
      (set! el (vector-ref rows x))
      (set! ch (if (string? el) (substring el y (+ y 1)) (list-ref el (+ y 1))))
      (if (integer? ch) (set! ch (integer->char ch)))
      (if (char? ch)(set! ch (string ch)))
      (set! p ch)
      (set! theText (car (gimp-text-fontname theImage theLayer
          (+ (* y 51.2) 12) (- (* x 51.2) 7)
          p    0    TRUE
          inFontSize PIXELS
          inFont)))

        ; go on next char
        ;(set! p (+ p 1))
        (set! y (+ y 1))
      )

    (set! y 0)
    (set! x (+ x 1))
  )

  ; apply the layers (apply transparence
  (gimp-image-merge-visible-layers theImage 0)

  (gimp-display-new theImage)
  )
)

(script-fu-register
  "script-fu-xytext-simple"           ;func name
  "XYText-simple"                     ;menu label
  "Generate font for xytext-simple"   ;description
  "Salahzar Stenvaag"                 ;author
  "copyright 2008, Salahzar Stenvaag" ;copyright notice
  "October 27, 1997"                  ;date created
  ""                                  ;image type that the script works on
  SF-STRING      "Text:"         "Text Box"           ;a string variable
  SF-FONT        "Font:"         "Andale Mono"        ;a fixed font variable
  SF-ADJUSTMENT  "Font size"     '(50 1 1000 1 10 0 1);a spin-button
  SF-COLOR       "Color:"        '(255 255 255)       ;color variable
)

(script-fu-menu-register "script-fu-xytext-simple" "<Toolbox>/Xtns/Text")

XyzzyText - 10 Character

Master Script

//////////////////////////////////////////// 
// XyzzyText v2.1(10-Char) by Thraxis Epsilon
// XyzzyText v2.1 Script (Set Line Color) by Huney Jewell
// XyzzyText v2.0 Script (5 Face, Single Texture) 
//
// Edited trivially by Joel Cloquet on 1/2011 to use the (relatively)
// new llSetLinkPrimitiveParamsFast function, thereby removing the need in
// some cases to use the slave script.
//
// Heavily Modified by Thraxis Epsilon, Gigs Taggart 5/2007 and Strife Onizuka 8/2007
// Rewrite to allow one-script-per-object operation w/ optional slaves
// Enable prim-label functionality
// Enabled Banking
// Enabled 10-char per prim
//
// Modified by Kermitt Quirk 19/01/2006 
// To add support for 5 face prim instead of 3 
// 
// Core XyText Originally Written by Xylor Baysklef 
// 
//
//////////////////////////////////////////// 

/////////////// CONSTANTS /////////////////// 
// XyText Message Map. 
integer DISPLAY_STRING      = 204000; 
integer DISPLAY_EXTENDED    = 204001; 
integer REMAP_INDICES       = 204002; 
integer RESET_INDICES       = 204003; 
integer SET_FADE_OPTIONS    = 204004; 
integer SET_FONT_TEXTURE    = 204005; 
integer SET_LINE_COLOR      = 204006; 
integer SET_COLOR           = 204007; 
integer RESCAN_LINKSET      = 204008;

//internal API
integer REGISTER_SLAVE      = 205000;
integer SLAVE_RECOGNIZED    = 205001;
integer SLAVE_DISPLAY       = 205003;
integer SLAVE_DISPLAY_EXTENDED = 205004;
integer SLAVE_RESET         = 205005;

// This is an extended character escape sequence.
string  ESCAPE_SEQUENCE = "\\e";

// This is used to get an index for the extended character.
string  EXTENDED_INDEX  = "123456789abcdef";


// Face numbers. 
integer FACE_1          = 3; 
integer FACE_2          = 7; 
integer FACE_3          = 4; 
integer FACE_4          = 6; 
integer FACE_5          = 1;

// Used to hide the text after a fade-out. 
key     TRANSPARENT     = "701917a8-d614-471f-13dd-5f4644e36e3c";
key     null_key        = NULL_KEY;

// This is a list of textures for all 2-character combinations. 
list    CHARACTER_GRID  = [ 
        "00e9f9f7-0669-181c-c192-7f8e67678c8d", 
        "347a5cb6-0031-7ec0-2fcf-f298eebf3c0e", 
        "4e7e689e-37f1-9eca-8596-a958bbd23963", 
        "19ea9c21-67ba-8f6f-99db-573b1b877eb1", 
        "dde7b412-cda1-652f-6fc2-73f4641f96e1", 
        "af6fa3bb-3a6c-9c4f-4bf5-d1c126c830da", 
        "a201d3a2-364b-43b6-8686-5881c0f82a94", 
        "b674dec8-fead-99e5-c28d-2db8e4c51540", 
        "366e05f3-be6b-e5cf-c33b-731dff649caa", 
        "75c4925c-0427-dc0c-c71c-e28674ff4d27", 
        "dcbe166b-6a97-efb2-fc8e-e5bc6a8b1be6", 
        "0dca2feb-fc66-a762-db85-89026a4ecd68", 
        "a0fca76f-503a-946b-9336-0a918e886f7a", 
        "67fb375d-89a1-5a4f-8c7a-0cd1c066ffc4", 
        "300470b2-da34-5470-074c-1b8464ca050c", 
        "d1f8e91c-ce2b-d85e-2120-930d3b630946", 
        "2a190e44-7b29-dadb-0bff-c31adaf5a170", 
        "75d55e71-f6f8-9835-e746-a45f189f30a1", 
        "300fac33-2b30-3da3-26bc-e2d70428ec19", 
        "0747c776-011a-53ce-13ee-8b5bb9e87c1e", 
        "85a855c3-a94f-01ca-33e0-7dde92e727e2", 
        "cbc1dab2-2d61-2986-1949-7a5235c954e1", 
        "f7aef047-f266-9596-16df-641010edd8e1", 
        "4c34ebf7-e5e1-2e1a-579f-e224d9d5e71b", 
        "4a69e98c-26a5-ad05-e92e-b5b906ad9ef9", 
        "462a9226-2a97-91ac-2d89-57ab33334b78", 
        "20b24b3a-8c57-82ee-c6ed-555003f5dbcd", 
        "9b481daa-9ea8-a9fa-1ee4-ab9a0d38e217", 
        "c231dbdc-c842-15b0-7aa6-6da14745cfdc", 
        "c97e3cbb-c9a3-45df-a0ae-955c1f4bf9cf", 
        "f1e7d030-ff80-a242-cb69-f6951d4eae3b", 
        "ed32d6c4-d733-c0f1-f242-6df1d222220d", 
        "88f96a30-dccf-9b20-31ef-da0dfeb23c72", 
        "252f2595-58b8-4bcc-6515-fa274d0cfb65", 
        "f2838c4f-de80-cced-dff8-195dfdf36b2c", 
        "cc2594fe-add2-a3df-cdb3-a61711badf53", 
        "e0ce2972-da00-955c-129e-3289b3676776", 
        "3e0d336d-321f-ddfa-5c1b-e26131766f6a", 
        "d43b1dc4-6b51-76a7-8b90-38865b82bf06", 
        "06d16cbb-1868-fd1d-5c93-eae42164a37d", 
        "dd5d98cf-273e-3fd0-f030-48be58ee3a0b", 
        "0e47c89e-de4a-6233-a2da-cb852aad1b00", 
        "fb9c4a55-0e13-495b-25c4-f0b459dc06de", 
        "e3ce8def-312c-735b-0e48-018b6799c883", 
        "2f713216-4e71-d123-03ed-9c8554710c6b", 
        "4a417d8a-1f4f-404b-9783-6672f8527911", 
        "ca5e21ec-5b20-5909-4c31-3f90d7316b33", 
        "06a4fcc3-e1c4-296d-8817-01f88fbd7367", 
        "130ac084-6f3c-95de-b5b6-d25c80703474", 
        "59d540a0-ae9d-3606-5ae0-4f2842b64cfa", 
        "8612ae9a-f53c-5bf4-2899-8174d7abc4fd", 
        "12467401-e979-2c49-34e0-6ac761542797", 
        "d53c3eaa-0404-3860-0675-3e375596c3e3", 
        "9f5b26bd-81d3-b25e-62fe-5b671d1e3e79", 
        "f57f0b64-a050-d617-ee00-c8e9e3adc9cb", 
        "beff166a-f5f3-f05e-e020-98f2b00e27ed", 
        "02278a65-94ba-6d5e-0d2b-93f2e4f4bf70", 
        "a707197d-449e-5b58-846c-0c850c61f9d6", 
        "021d4b1a-9503-a44f-ee2b-976eb5d80e68", 
        "0ae2ffae-7265-524d-cb76-c2b691992706"];
list    CHARACTER_GRID2  = [         
        "f6e41cf2-1104-bd0b-0190-dffad1bac813", 
        "2b4bb15e-956d-56ae-69f5-d26a20de0ce7", 
        "f816da2c-51f1-612a-2029-a542db7db882", 
        "345fea05-c7be-465c-409f-9dcb3bd2aa07", 
        "b3017e02-c063-5185-acd5-1ef5f9d79b89", 
        "4dcff365-1971-3c2b-d73c-77e1dc54242a" 
          ]; 

///////////// END CONSTANTS ////////////////

///////////// GLOBAL VARIABLES /////////////// 
// All displayable characters.  Default to ASCII order. 
string gCharIndex; 
// This is the channel to listen on while acting 
// as a cell in a larger display. 
integer gCellChannel      = -1; 
// This is the starting character position in the cell channel message 
// to render. 
integer gCellCharPosition = 0;
// This is whether or not to use the fade in/out special effect. 
integer gCellUseFading      = FALSE; 
// This is how long to display the text before fading out (if using 
// fading special effect). 
// Note: < 0  means don't fade out. 
float   gCellHoldDelay      = 1.0; 

integer gSlaveRegistered;
list gSlaveNames;

integer BANK_STRIDE=3; //offset, length, highest_dirty
list gBankingData;

/////////// END GLOBAL VARIABLES //////////// 

ResetCharIndex() { 
    gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"; 
    // \" <-- Fixes LSL syntax highlighting bug. 
    gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~"; 
    gCharIndex += "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"; 
} 

vector GetGridPos(integer index1, integer index2) { 
    // There are two ways to use the lookup table... 
    integer Col; 
    integer Row; 
    if (index1 >= index2) { 
        // In this case, the row is the index of the first character: 
        Row = index1; 
        // And the col is the index of the second character (x2) 
        Col = index2 * 2; 
    } 
    else { // Index1 < Index2 
        // In this case, the row is the index of the second character: 
        Row = index2; 
        // And the col is the index of the first character, x2, offset by 1. 
        Col = index1 * 2 + 1; 
    } 
    return <Col, Row, 0>; 
} 

string GetGridTexture(vector grid_pos) { 
    // Calculate the texture in the grid to use. 
    integer GridCol = llRound(grid_pos.x) / 20; 
    integer GridRow = llRound(grid_pos.y) / 10; 

    // Lookup the texture. 
    key Texture = llList2Key(CHARACTER_GRID, GridRow * (GridRow + 1) / 2 + GridCol); 
    return Texture; 
} 

vector GetGridOffset(vector grid_pos) { 
    // Zoom in on the texture showing our character pair. 
    integer Col = llRound(grid_pos.x) % 20; 
    integer Row = llRound(grid_pos.y) % 10; 

    // Return the offset in the texture. 
    return <-0.45 + 0.05 * Col, 0.45 - 0.1 * Row, 0.0>; 
} 

ShowChars(integer link,vector grid_pos1, vector grid_pos2, vector grid_pos3, vector grid_pos4, vector grid_pos5) { 
   // Set the primitive textures directly. 
     
               
   llSetLinkPrimitiveParamsFast( link , [ 
        PRIM_TEXTURE, FACE_1, GetGridTexture(grid_pos1), <0.25, 0.1, 0>, GetGridOffset(grid_pos1) + <0.075, 0, 0>, 0.0, 
        PRIM_TEXTURE, FACE_2, GetGridTexture(grid_pos2), <0.1, 0.1, 0>, GetGridOffset(grid_pos2), 0.0, 
        PRIM_TEXTURE, FACE_3, GetGridTexture(grid_pos3), <-1.48, 0.1, 0>, GetGridOffset(grid_pos3)+ <0.37, 0, 0>, 0.0, 
        PRIM_TEXTURE, FACE_4, GetGridTexture(grid_pos4), <0.1, 0.1, 0>, GetGridOffset(grid_pos4), 0.0, 
        PRIM_TEXTURE, FACE_5, GetGridTexture(grid_pos5), <0.25, 0.1, 0>, GetGridOffset(grid_pos5) - <0.075, 0, 0>, 0.0 
        ]); 
}

RenderString(integer link, string str) {
    // Get the grid positions for each pair of characters. 
    vector GridPos1 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)), 
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) ); 
    vector GridPos2 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)), 
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) ); 
    vector GridPos3 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)), 
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 5, 5)) ); 
    vector GridPos4 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 6, 6)), 
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 7, 7)) ); 
    vector GridPos5 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 8, 8)), 
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 9, 9)) );                                   

    // Use these grid positions to display the correct textures/offsets. 
    ShowChars(link,GridPos1, GridPos2, GridPos3, GridPos4, GridPos5); 
}

//RenderWithEffects(integer link, string str) { 
//    // Get the grid positions for each pair of characters. 
//    vector GridPos1 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)), 
//                                  llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) ); 
//    vector GridPos2 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)), 
//                                  llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) ); 
//    vector GridPos3 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)), 
//                                  llSubStringIndex(gCharIndex, llGetSubString(str, 5, 5)) ); 
//    vector GridPos4 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 6, 6)), 
//                                  llSubStringIndex(gCharIndex, llGetSubString(str, 7, 7)) ); 
//    vector GridPos5 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 8, 8)), 
//                                  llSubStringIndex(gCharIndex, llGetSubString(str, 9, 9)) );                          //         
//
//      // First set the alpha to the lowest possible. 
//   llSetAlpha(0.05, ALL_SIDES); 
//
//    // Use these grid positions to display the correct textures/offsets. 
//    ShowChars(link,GridPos1, GridPos2, GridPos3, GridPos4, GridPos5);
//
//    float Alpha = 0.10; 
//    for (; Alpha <= 1.0; Alpha += 0.05)  
//       llSetAlpha(Alpha, ALL_SIDES); 
//          // See if we want to fade out as well. 
//   if (gCellHoldDelay < 0.0) 
//       // No, bail out. (Just keep showing the string at full strength). 
//       return; 
//          // Hold the text for a while. 
//   llSleep(gCellHoldDelay); 
//      // Now fade out. 
//   for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05) 
//       llSetAlpha(Alpha, ALL_SIDES); 
//          // Make the text transparent to fully hide it. 
//   llSetTexture(TRANSPARENT, ALL_SIDES); 
//} 

integer RenderExtended(integer link, string str,integer render) {
    // Look for escape sequences.
    integer length = 0;
    list Parsed       = llParseString2List(str, [], (list)ESCAPE_SEQUENCE);
    integer ParsedLen = llGetListLength(Parsed);

    // Create a list of index values to work with.
    list Indices;
    // We start with room for 6 indices.
    integer IndicesLeft = 10;

    string Token;
    integer Clipped;
    integer LastWasEscapeSequence = FALSE;
    // Work from left to right.
    integer i=0;
    for (; i < ParsedLen && IndicesLeft > 0; ++i) {
        Token = llList2String(Parsed, i);

        // If this is an escape sequence, just set the flag and move on.
        if (Token == ESCAPE_SEQUENCE) {
            LastWasEscapeSequence = TRUE;
        }
        else { // Token != ESCAPE_SEQUENCE
            // Otherwise this is a normal token.  Check its length.
            Clipped = FALSE;
            integer TokenLength = llStringLength(Token);
            // Clip if necessary.
            if (TokenLength > IndicesLeft) {
                TokenLength = llStringLength(Token = llGetSubString(Token, 0, IndicesLeft - 1));
                IndicesLeft = 0;
                Clipped = TRUE;
            }
            else
                IndicesLeft -= TokenLength;

            // Was the previous token an escape sequence?
            if (LastWasEscapeSequence) {
                // Yes, the first character is an escape character, the rest are normal.
                length += 2 + TokenLength;
                if (render)
                {
                    // This is the extended character.
                    Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
                    
                    // These are the normal characters.
                    integer j=1;
                    for (; j < TokenLength; ++j)
                    {
                        Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
                    }
                }
            }
            else { // Normal string.
                // Just add the characters normally.
                length += TokenLength;
                if(render)
                {
                    integer j=0;
                    for (; j < TokenLength; ++j)
                    {
                        Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
                    }
                }
            }

            // Unset this flag, since this was not an escape sequence.
            LastWasEscapeSequence = FALSE;
        }
    }

    if(render)
    {
        // Use the indices to create grid positions.
        vector GridPos1 = GetGridPos( llList2Integer(Indices, 0), llList2Integer(Indices, 1) );
        vector GridPos2 = GetGridPos( llList2Integer(Indices, 2), llList2Integer(Indices, 3) );
        vector GridPos3 = GetGridPos( llList2Integer(Indices, 4), llList2Integer(Indices, 5) );
        vector GridPos4 = GetGridPos( llList2Integer(Indices, 6), llList2Integer(Indices, 7) );
        vector GridPos5 = GetGridPos( llList2Integer(Indices, 8), llList2Integer(Indices, 9) );
        
        // Use these grid positions to display the correct textures/offsets.
        ShowChars(link,GridPos1, GridPos2, GridPos3, GridPos4, GridPos5);
    }
    return length;
}


integer ConvertIndex(integer index) {
    // This converts from an ASCII based index to our indexing scheme.
    if (index >= 32) // ' ' or higher
        index -= 32;
    else { // index < 32
        // Quick bounds check.
        if (index > 15)
            index = 15;

        index += 94; // extended characters
    }

    return index;
}


PassToRender(integer render,string message, integer bank)
{
    float time;
    integer extendedlen = 0;
    integer link;
    
    integer i = 0;
    integer msgLen = llStringLength(message);
    string TextToRender;
    integer num_slaves=llGetListLength(gSlaveNames);
    string slave_name; //avoids unnecessary casts, keeping it as a string
    

    //get the bank offset and length
    integer bank_offset=llList2Integer(gBankingData, (bank * BANK_STRIDE));
    integer bank_length=llList2Integer(gBankingData, (bank * BANK_STRIDE) + 1);
    integer bank_highest_dirty=llList2Integer(gBankingData, (bank * BANK_STRIDE) + 2);

    integer x=0;    
    for (;x < msgLen;x = x + 10)
    {

        if (i >= bank_length)  //we don't want to run off the end of the bank
        {
            //set the dirty to max, and bail out, we're done
            gBankingData=llListReplaceList(gBankingData, [bank_length], (bank * BANK_STRIDE) + 2, (bank * BANK_STRIDE) + 2);
            return;
        }   
        
        link = unpack(gXyTextPrims,(i + bank_offset));
        TextToRender = llGetSubString(message, x, x + 20);
        
        if(gSlaveRegistered && (link % (num_slaves +1)))
        {
            slave_name=llList2String(gSlaveNames, (link % (num_slaves + 1)) - 1);
            if (render == 1)
                llMessageLinked(LINK_THIS, SLAVE_DISPLAY, TextToRender, (key)((string)link + "," + slave_name));
            if (render == 2)
            {
                if(llSubStringIndex(TextToRender,"\e")>x+10)
                {
                    extendedlen = 10;
                }
                else
                {
                    extendedlen = RenderExtended(link,TextToRender,0);
                }

                if(extendedlen>10)
                {
                    x += extendedlen-10;
                }

                llMessageLinked(LINK_THIS,SLAVE_DISPLAY_EXTENDED,TextToRender,(key)((string)link+","+slave_name));
            }        
        }
        else
        {
            if (render == 1)
                RenderString(link,TextToRender);
            if (render == 2)
            {
                extendedlen = RenderExtended(link,TextToRender,1);
                if(extendedlen>10)
                {
                    x += extendedlen-10;
                }
            }
            
//            if (render == 3)
//                RenderWithEffects(link,TextToRender);
        }
        ++i;            
    }
    
    if (bank_highest_dirty==0)
        bank_highest_dirty=bank_length;
    
    integer current_highest_dirty=i;
    while (i < bank_highest_dirty)
    {
        link = unpack(gXyTextPrims,(i + bank_offset));
        
        if(gSlaveRegistered && (link % (num_slaves+1) != 0))
        {
            slave_name=llList2String(gSlaveNames, (link % (num_slaves + 1)) - 1);
            llMessageLinked(LINK_THIS, SLAVE_DISPLAY, "     ", (key)((string)link + "," + slave_name));       
            //sorry, no fade effect with slave
        }
        else
        {
            RenderString(link,"          ");
        }
        ++i;        
    }
    gBankingData=llListReplaceList(gBankingData, [current_highest_dirty], (bank * BANK_STRIDE) + 2, (bank * BANK_STRIDE) + 2);
}

// Bitwise Voodoo by Gigs Taggart and optimized by Strife Onizuka
list gXyTextPrims;


integer get_number_of_prims()
{//ignores avatars.
    integer a = llGetNumberOfPrims();
    while(llGetAgentSize(llGetLinkKey(a)))
        --a;
    return a;
}

//functions to pack 8-bit shorts into ints
list pack_and_insert(list in_list, integer pos, integer value)
{
//    //figure out the bitpack position
//    integer pack = pos & 3; //4 bytes per int
//    pos=pos >> 2;
//    integer shifted = value << (pack << 3);
//    integer old_value = llList2Integer(in_list, pos);
//    shifted = old_value | shifted;
//    in_list = llListReplaceList(in_list, (list)shifted, pos, pos);
//    return in_list;
    //Safe optimized version
    integer index = pos >> 2;
    return llListReplaceList(in_list, (list)(llList2Integer(in_list, index) | (value << ((pos & 3) << 3))), index, index);
}

integer unpack(list in_list, integer pos)
{
    return (llList2Integer(in_list, pos >> 2) >> ((pos & 3) << 3)) & 0x000000FF;//unsigned
//    return (llList2Integer(in_list, pos >> 2) << (((~pos) & 3) << 3)) >> 24;//signed
}


change_color(vector color)
{
    integer num_prims=llGetListLength(gXyTextPrims) << 2;
    
    integer i = 0;
    
    for (; i<=num_prims; ++i)
    {
        integer link = unpack(gXyTextPrims,i);
        if (!link)
            return;
        
        llSetLinkPrimitiveParamsFast( link,[ 
            PRIM_COLOR, FACE_1, color, 1.0,
            PRIM_COLOR, FACE_2, color, 1.0,
            PRIM_COLOR, FACE_3, color, 1.0,
            PRIM_COLOR, FACE_4, color, 1.0,
            PRIM_COLOR, FACE_5, color, 1.0
        ]);
    }
}

change_line_color(integer bank, vector color)
{    

    //get the bank offset and length
    integer i = llList2Integer(gBankingData, (bank * BANK_STRIDE));
    integer bank_end = i + llList2Integer(gBankingData, (bank * BANK_STRIDE) + 1);

    for (; i < bank_end; ++i)
    {     
        integer link = unpack(gXyTextPrims,i);
        if (!link)
            return;
        
        llSetLinkPrimitiveParamsFast( link,[ 
            PRIM_COLOR, FACE_1, color, 1.0,
            PRIM_COLOR, FACE_2, color, 1.0,
            PRIM_COLOR, FACE_3, color, 1.0,
            PRIM_COLOR, FACE_4, color, 1.0,
            PRIM_COLOR, FACE_5, color, 1.0
        ]);
    }
}

init()
{
    integer num_prims=get_number_of_prims();
 
    string link_name;
    integer bank=0;
    integer bank_empty=FALSE;
    integer prims_pointer=0; //"pointer" to the next entry to be used in the gXyTextPrims list.
 
    list temp_bank=[];
    integer temp_bank_stride=2;
 
    // moving this before the prim scan so that the slaves properly configure themseves before
    // any requests to display
    llMessageLinked(LINK_THIS, SLAVE_RESET, "" , null_key);
  
    gXyTextPrims=[];
    integer x=0;
    for (;x<64;++x)
    {
        gXyTextPrims= (gXyTextPrims = []) + gXyTextPrims + [0];  //we need to pad out the list to make it easier to add things in any order later
    }

    gBankingData = [];
    @loop;
    {
        //loop over all prims, looking for ones in the current bank
        for(x=0;x<=num_prims;++x)
        {
            link_name=llGetLinkName(x);
            list tmp = llParseString2List(link_name, ["-"], []);
            if(llList2String(tmp,0)== "xyzzytext")
            {
                integer prims_bank=llList2Integer(tmp,1);
                if (llList2Integer(tmp,1)==bank)
                {
                    temp_bank+=llList2Integer(tmp,2) + (list)x;
                }
            }
 
        }
 
 
        if (temp_bank!=[])
        {
            //sort the current bank
            temp_bank=llListSort(temp_bank, temp_bank_stride, TRUE);
 
            integer temp_len=llGetListLength(temp_bank);
 
            //store metadata
            gBankingData+=[prims_pointer,temp_len/temp_bank_stride,0];
 
            //repack the bank into the prim list
            for (x=0; x < temp_len; x+=temp_bank_stride)
            {
                gXyTextPrims = pack_and_insert(gXyTextPrims, prims_pointer, llList2Integer(temp_bank, x+1));
                ++prims_pointer;
            }
            ++bank;
            temp_bank=[];
            jump loop;
        }
    }
}

default { 
    state_entry() { 
        // Initialize the character index. 
        ResetCharIndex();
        init();
    } 

   on_rez(integer num)
   {
      llResetScript();       
   }
   
    link_message(integer sender, integer channel, string data, key id) {
        if(id == null_key)
            id="0";
        
        if (channel == DISPLAY_STRING) { 
            PassToRender(1,data, (integer)((string)id)); 
            return; 
        } 
        else if (channel == DISPLAY_EXTENDED) { 
             PassToRender(2,data, (integer)((string)id)); 
            return; 
        }
        else if (channel == REMAP_INDICES) {
            // Parse the message, splitting it up into index values.
            list Parsed = llCSV2List(data);
            integer i;
            // Go through the list and swap each pair of indices.
            for (i = 0; i < llGetListLength(Parsed); i += 2) {
                integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
                integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );

                // Swap these index values.
                string Value1 = llGetSubString(gCharIndex, Index1, Index1);
                string Value2 = llGetSubString(gCharIndex, Index2, Index2);

                gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
                gCharIndex = llInsertString(gCharIndex, Index1, Value2);

                gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
                gCharIndex = llInsertString(gCharIndex, Index2, Value1);
            }
            return;
        }
        else if (channel == RESET_INDICES) {
            // Restore the character index back to default settings.
            ResetCharIndex();
            return;
        }        
        else if (channel == RESCAN_LINKSET)
        {
            init();
        }
        else if (channel == SET_COLOR) {
            change_color((vector)data); 
        }
        else if (channel == SET_LINE_COLOR) {
            change_line_color((integer)((string)id), (vector)data); 
        }     
        else if (channel == REGISTER_SLAVE)
        {
            if(!~llListFindList(gSlaveNames, (list)data))
            {//isn't registered yet
                gSlaveNames += data;
                gSlaveRegistered=TRUE;
                //llOwnerSay((string)llGetListLength(gSlaveNames) + " Slave(s) Recognized: " + data);
            }
//            else
//            {//it already exists
//                llOwnerSay((string)llGetListLength(gSlaveNames) + " Slave, Existing Slave Recognized: " + data);
//            }
            llMessageLinked(LINK_THIS, SLAVE_RECOGNIZED, data , null_key);
        }
    }

    changed(integer change)
    {
        if(change&CHANGED_INVENTORY)
        {
            if(gSlaveRegistered)        
            {
                //by using negative indexes they don't need to be adjusted when an entry is deleted.
                integer x = ~llGetListLength(gSlaveNames);
                while(++x)
                {
                    if (!~llGetInventoryType(llList2String(gSlaveNames, x)))
                    {
                        //llOwnerSay("Slave Removed: " + llList2String(gSlaveNames, x));
                        gSlaveNames = llDeleteSubList(gSlaveNames, x, x);
                    }
                }
                gSlaveRegistered = !(gSlaveNames == []);
            }
        }
    }
}

Slave Script

////////////////////////////////////////////
// XyzzyText v2.1(10-Char) by Thraxis Epsilon
// XyText v2.0 SLAVE Script (5 Face, Single Texture) 
//
// Heavily Modified by Thraxis Epsilon, Gigs Taggart 5/2007 and Strife Onizuka 8/2007
// Rewrite to allow one-script-per-object operation w/ optional slaves
// Enable prim-label functionality
// Enabled Banking
// Enabled 10-char per prim
//
// Modified by Kermitt Quirk 19/01/2006 
// To add support for 5 face prim instead of 3 
// 
// Originally Written by Xylor Baysklef 
// 
//
//////////////////////////////////////////// 


integer REMAP_INDICES       = 204002; 
integer RESET_INDICES       = 204003;

//internal API
integer REGISTER_SLAVE      = 205000;
integer SLAVE_RECOGNIZED    = 205001;
integer SLAVE_DISPLAY       = 205003;
integer SET_FONT_TEXTURE    = 204005; 

integer SLAVE_DISPLAY_EXTENDED = 205004;
integer SLAVE_RESET = 205005;

// This is an extended character escape sequence.
string  ESCAPE_SEQUENCE = "\\e";

// This is used to get an index for the extended character.
string  EXTENDED_INDEX  = "123456789abcdef";

// Face numbers. 
integer FACE_1          = 3; 
integer FACE_2          = 7; 
integer FACE_3          = 4; 
integer FACE_4          = 6; 
integer FACE_5          = 1;

// This is a list of textures for all 2-character combinations. 
list    CHARACTER_GRID  = [ 
        "00e9f9f7-0669-181c-c192-7f8e67678c8d", 
        "347a5cb6-0031-7ec0-2fcf-f298eebf3c0e", 
        "4e7e689e-37f1-9eca-8596-a958bbd23963", 
        "19ea9c21-67ba-8f6f-99db-573b1b877eb1", 
        "dde7b412-cda1-652f-6fc2-73f4641f96e1", 
        "af6fa3bb-3a6c-9c4f-4bf5-d1c126c830da", 
        "a201d3a2-364b-43b6-8686-5881c0f82a94", 
        "b674dec8-fead-99e5-c28d-2db8e4c51540", 
        "366e05f3-be6b-e5cf-c33b-731dff649caa", 
        "75c4925c-0427-dc0c-c71c-e28674ff4d27", 
        "dcbe166b-6a97-efb2-fc8e-e5bc6a8b1be6", 
        "0dca2feb-fc66-a762-db85-89026a4ecd68", 
        "a0fca76f-503a-946b-9336-0a918e886f7a", 
        "67fb375d-89a1-5a4f-8c7a-0cd1c066ffc4", 
        "300470b2-da34-5470-074c-1b8464ca050c", 
        "d1f8e91c-ce2b-d85e-2120-930d3b630946", 
        "2a190e44-7b29-dadb-0bff-c31adaf5a170", 
        "75d55e71-f6f8-9835-e746-a45f189f30a1", 
        "300fac33-2b30-3da3-26bc-e2d70428ec19", 
        "0747c776-011a-53ce-13ee-8b5bb9e87c1e", 
        "85a855c3-a94f-01ca-33e0-7dde92e727e2", 
        "cbc1dab2-2d61-2986-1949-7a5235c954e1", 
        "f7aef047-f266-9596-16df-641010edd8e1", 
        "4c34ebf7-e5e1-2e1a-579f-e224d9d5e71b", 
        "4a69e98c-26a5-ad05-e92e-b5b906ad9ef9", 
        "462a9226-2a97-91ac-2d89-57ab33334b78", 
        "20b24b3a-8c57-82ee-c6ed-555003f5dbcd", 
        "9b481daa-9ea8-a9fa-1ee4-ab9a0d38e217", 
        "c231dbdc-c842-15b0-7aa6-6da14745cfdc", 
        "c97e3cbb-c9a3-45df-a0ae-955c1f4bf9cf", 
        "f1e7d030-ff80-a242-cb69-f6951d4eae3b", 
        "ed32d6c4-d733-c0f1-f242-6df1d222220d", 
        "88f96a30-dccf-9b20-31ef-da0dfeb23c72", 
        "252f2595-58b8-4bcc-6515-fa274d0cfb65", 
        "f2838c4f-de80-cced-dff8-195dfdf36b2c", 
        "cc2594fe-add2-a3df-cdb3-a61711badf53", 
        "e0ce2972-da00-955c-129e-3289b3676776", 
        "3e0d336d-321f-ddfa-5c1b-e26131766f6a", 
        "d43b1dc4-6b51-76a7-8b90-38865b82bf06", 
        "06d16cbb-1868-fd1d-5c93-eae42164a37d", 
        "dd5d98cf-273e-3fd0-f030-48be58ee3a0b", 
        "0e47c89e-de4a-6233-a2da-cb852aad1b00", 
        "fb9c4a55-0e13-495b-25c4-f0b459dc06de", 
        "e3ce8def-312c-735b-0e48-018b6799c883", 
        "2f713216-4e71-d123-03ed-9c8554710c6b", 
        "4a417d8a-1f4f-404b-9783-6672f8527911", 
        "ca5e21ec-5b20-5909-4c31-3f90d7316b33", 
        "06a4fcc3-e1c4-296d-8817-01f88fbd7367", 
        "130ac084-6f3c-95de-b5b6-d25c80703474", 
        "59d540a0-ae9d-3606-5ae0-4f2842b64cfa", 
        "8612ae9a-f53c-5bf4-2899-8174d7abc4fd", 
        "12467401-e979-2c49-34e0-6ac761542797", 
        "d53c3eaa-0404-3860-0675-3e375596c3e3", 
        "9f5b26bd-81d3-b25e-62fe-5b671d1e3e79", 
        "f57f0b64-a050-d617-ee00-c8e9e3adc9cb", 
        "beff166a-f5f3-f05e-e020-98f2b00e27ed", 
        "02278a65-94ba-6d5e-0d2b-93f2e4f4bf70", 
        "a707197d-449e-5b58-846c-0c850c61f9d6", 
        "021d4b1a-9503-a44f-ee2b-976eb5d80e68", 
        "0ae2ffae-7265-524d-cb76-c2b691992706", 
        "f6e41cf2-1104-bd0b-0190-dffad1bac813", 
        "2b4bb15e-956d-56ae-69f5-d26a20de0ce7", 
        "f816da2c-51f1-612a-2029-a542db7db882", 
        "345fea05-c7be-465c-409f-9dcb3bd2aa07"];
list    CHARACTER_GRID2  = [         
        "b3017e02-c063-5185-acd5-1ef5f9d79b89", 
        "4dcff365-1971-3c2b-d73c-77e1dc54242a" 
          ]; 

///////////// END CONSTANTS ////////////////
string gCharIndex; 

integer gActive; //if we are recognized, this is true
/////////// END GLOBAL VARIABLES ////////////
ResetCharIndex() { 
    gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"; 
    // \" <-- Fixes LSL syntax highlighting bug. 
    gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~"; 
    gCharIndex += "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"; 
} 

vector GetGridPos(integer index1, integer index2) { 
    // There are two ways to use the lookup table... 
    integer Col; 
    integer Row; 
    if (index1 >= index2) { 
        // In this case, the row is the index of the first character: 
        Row = index1; 
        // And the col is the index of the second character (x2) 
        Col = index2 * 2; 
    } 
    else { // Index1 < Index2 
        // In this case, the row is the index of the second character: 
        Row = index2; 
        // And the col is the index of the first character, x2, offset by 1. 
        Col = index1 * 2 + 1; 
    } 
    return <Col, Row, 0>; 
} 

string GetGridTexture(vector grid_pos) { 
    // Calculate the texture in the grid to use. 
    integer GridCol = llRound(grid_pos.x) / 20; 
    integer GridRow = llRound(grid_pos.y) / 10; 

    // Lookup the texture. 
    key Texture = llList2Key(CHARACTER_GRID, GridRow * (GridRow + 1) / 2 + GridCol); 
    return Texture; 
} 

vector GetGridOffset(vector grid_pos) { 
    // Zoom in on the texture showing our character pair. 
    integer Col = llRound(grid_pos.x) % 20; 
    integer Row = llRound(grid_pos.y) % 10; 

    // Return the offset in the texture. 
    return <-0.45 + 0.05 * Col, 0.45 - 0.1 * Row, 0.0>; 
} 

ShowChars(integer link,vector grid_pos1, vector grid_pos2, vector grid_pos3, vector grid_pos4, vector grid_pos5) { 
   // Set the primitive textures directly. 
     
               
   llSetLinkPrimitiveParamsFast( link , [ 
        PRIM_TEXTURE, FACE_1, GetGridTexture(grid_pos1), <0.25, 0.1, 0>, GetGridOffset(grid_pos1) + <0.075, 0, 0>, 0.0, 
        PRIM_TEXTURE, FACE_2, GetGridTexture(grid_pos2), <0.1, 0.1, 0>, GetGridOffset(grid_pos2), 0.0, 
        PRIM_TEXTURE, FACE_3, GetGridTexture(grid_pos3), <-1.48, 0.1, 0>, GetGridOffset(grid_pos3)+ <0.37, 0, 0>, 0.0, 
        PRIM_TEXTURE, FACE_4, GetGridTexture(grid_pos4), <0.1, 0.1, 0>, GetGridOffset(grid_pos4), 0.0, 
        PRIM_TEXTURE, FACE_5, GetGridTexture(grid_pos5), <0.25, 0.1, 0>, GetGridOffset(grid_pos5) - <0.075, 0, 0>, 0.0 
        ]); 
}

RenderString(integer link, string str) {
    // Get the grid positions for each pair of characters. 
    vector GridPos1 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)), 
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) ); 
    vector GridPos2 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)), 
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) ); 
    vector GridPos3 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)), 
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 5, 5)) ); 
    vector GridPos4 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 6, 6)), 
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 7, 7)) ); 
    vector GridPos5 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 8, 8)), 
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 9, 9)) );                                   

    // Use these grid positions to display the correct textures/offsets. 
    ShowChars(link,GridPos1, GridPos2, GridPos3, GridPos4, GridPos5); 
}

RenderExtended(integer link, string str) {
    // Look for escape sequences.
    list Parsed       = llParseString2List(str, [], (list)ESCAPE_SEQUENCE);
    integer ParsedLen = llGetListLength(Parsed);

    // Create a list of index values to work with.
    list Indices;
    // We start with room for 6 indices.
    integer IndicesLeft = 10;

    string Token;
    integer Clipped;
    integer LastWasEscapeSequence = FALSE;
    // Work from left to right.
    integer i=0;
    for (; i < ParsedLen && IndicesLeft > 0; ++i) {
        Token = llList2String(Parsed, i);

        // If this is an escape sequence, just set the flag and move on.
        if (Token == ESCAPE_SEQUENCE) {
            LastWasEscapeSequence = TRUE;
        }
        else { // Token != ESCAPE_SEQUENCE
            // Otherwise this is a normal token.  Check its length.
            Clipped = FALSE;
            integer TokenLength = llStringLength(Token);
            // Clip if necessary.
            if (TokenLength > IndicesLeft) {
                TokenLength = llStringLength(Token = llGetSubString(Token, 0, IndicesLeft - 1));
                IndicesLeft = 0;
                Clipped = TRUE;
            }
            else
                IndicesLeft -= TokenLength;

            // Was the previous token an escape sequence?
            if (LastWasEscapeSequence) {
                // Yes, the first character is an escape character, the rest are normal.
                // This is the extended character.
                Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
                
                // These are the normal characters.
                integer j=1;
                for (; j < TokenLength; ++j)
                {
                    Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
                }
            }
            else { // Normal string.
                // Just add the characters normally.
                integer j=0;
                for (; j < TokenLength; ++j)
                {
                    Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
                }
            }

            // Unset this flag, since this was not an escape sequence.
            LastWasEscapeSequence = FALSE;
        }
    }

    // Use the indices to create grid positions.
    vector GridPos1 = GetGridPos( llList2Integer(Indices, 0), llList2Integer(Indices, 1) );
    vector GridPos2 = GetGridPos( llList2Integer(Indices, 2), llList2Integer(Indices, 3) );
    vector GridPos3 = GetGridPos( llList2Integer(Indices, 4), llList2Integer(Indices, 5) );
    vector GridPos4 = GetGridPos( llList2Integer(Indices, 6), llList2Integer(Indices, 7) );
    vector GridPos5 = GetGridPos( llList2Integer(Indices, 8), llList2Integer(Indices, 9) );
    
    // Use these grid positions to display the correct textures/offsets.
    ShowChars(link,GridPos1, GridPos2, GridPos3, GridPos4, GridPos5);
}

integer ConvertIndex(integer index) {
    // This converts from an ASCII based index to our indexing scheme.
    if (index >= 32) // ' ' or higher
        index -= 32;
    else { // index < 32
        // Quick bounds check.
        if (index > 15)
            index = 15;

        index += 94; // extended characters
    }

    return index;
}


default { 
    state_entry() 
    { 
        // Initialize the character index. 
        ResetCharIndex();
        llMessageLinked(LINK_THIS, REGISTER_SLAVE, llGetScriptName() , NULL_KEY);  
    } 

   on_rez(integer num)
   {
      llResetScript();       
   }
   
    link_message(integer sender, integer channel, string data, key id) { 
        if (channel == SLAVE_RECOGNIZED)
        {
            if (data == llGetScriptName())
            {
                gActive=TRUE;
            }
            return;
        }
    
        else if (channel == SLAVE_DISPLAY) 
        { 
            if (!gActive)  
                return;
                
            list params=llCSV2List((string)id);
            if (llList2String(params, 1) != llGetScriptName())
                return;
            
            
            RenderString(llList2Integer(params, 0),data);
            return; 
        } 
    
       else if (channel == SLAVE_DISPLAY_EXTENDED) 
       {
            if (!gActive)  
                return;
                
            list params=llCSV2List((string)id);
            if (llList2String(params, 1) != llGetScriptName())
                return;
                
            RenderExtended(llList2Integer(params, 0),data);
    }
        else if (channel == REMAP_INDICES) {
            // Parse the message, splitting it up into index values.
            list Parsed = llCSV2List(data);
            integer i;
            // Go through the list and swap each pair of indices.
            for (i = 0; i < llGetListLength(Parsed); i += 2) {
                integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
                integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );

                // Swap these index values.
                string Value1 = llGetSubString(gCharIndex, Index1, Index1);
                string Value2 = llGetSubString(gCharIndex, Index2, Index2);

                gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
                gCharIndex = llInsertString(gCharIndex, Index1, Value2);

                gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
                gCharIndex = llInsertString(gCharIndex, Index2, Value1);
            }
            return;
        }
        else if (channel == RESET_INDICES) {
            // Restore the character index back to default settings.
            ResetCharIndex();
            return;
        }                 
       else if (channel == SLAVE_RESET)
        {
            ResetCharIndex();
            gActive=FALSE;
            llMessageLinked(LINK_THIS, REGISTER_SLAVE, llGetScriptName() , NULL_KEY);
        }
    }    
}

10-Char Prim Setup

////////////////////////////////////////////
// XyzzyText Prim Setup Script (5 Face)
//
// Modified by Thraxis Epsilon
//
////////////////////////////////////////////

default
{
    state_entry()
    {

        llSetPrimitiveParams([
                    PRIM_TYPE, PRIM_TYPE_PRISM, 32, <0.199, 0.8, 0.0>, 0.30, <0.0, 0.0, 0.0>, <1.0, 1.0, 0.0>, <0.0, 0.0, 0.0>,
                    PRIM_SIZE, <0.03, 2.89, 0.5>, 
                    PRIM_TEXTURE, 1, "09b04244-9569-d21f-6de0-4bbcf5552222", <2.48, 1.0, 0.0>, <-0.74, 0.0, 0.0>, 0.0, 
                    PRIM_TEXTURE, 6, "09b04244-9569-d21f-6de0-4bbcf5552222", <1.0, 1.0, 0.0>, <0.0, 0.0, 0.0>, 0.0, 
                    PRIM_TEXTURE, 4, "09b04244-9569-d21f-6de0-4bbcf5552222", <14.75, 1.0, 0.0>, <0.27, 0.0, 0.0>, 0.0, 
                    PRIM_TEXTURE, 7, "09b04244-9569-d21f-6de0-4bbcf5552222", <1.0, 1.0, 0.0>, <0.0, 0.0, 0.0>, 0.0, 
                    PRIM_TEXTURE, 3, "09b04244-9569-d21f-6de0-4bbcf5552222", <2.48, 1.0, 0.0>, <-0.25, 0.0, 0.0>, 0.0]);
                    
        llRemoveInventory(llGetScriptName());
    } 
}

XyzzyText - 16 Character MESH PLANE (Modification by Traven Sachs)

Master Script

//////////////////////////////////////////// 
// XyzzyText v2.1m (16-Char MESH) by Traven Sachs 29-November-2012
// XyzzyText v2.1(10-Char) by Thraxis Epsilon
// XyzzyText v2.1 Script (Set Line Color) by Huney Jewell
// XyzzyText v2.0 Script (5 Face, Single Texture) 
//
// Heavily Modified by Thraxis Epsilon, Gigs Taggart 5/2007 and Strife Onizuka 8/2007
// Rewrite to allow one-script-per-object operation w/ optional slaves
// Enable prim-label functionality
// Enabled Banking
// Enabled 10-char per prim
// Enabled 16-Char on an 8 Face Mesh Plane Prim
//
// Modified by Kermitt Quirk 19/01/2006 
// To add support for 5 face prim instead of 3 
// 
// Core XyText Originally Written by Xylor Baysklef 
//
//////////////////////////////////////////// 

/////////////// CONSTANTS /////////////////// 
// XyText Message Map. 
integer DISPLAY_STRING      = 204000; 
integer DISPLAY_EXTENDED    = 204001; 
integer REMAP_INDICES       = 204002; 
integer RESET_INDICES       = 204003; 
integer SET_FADE_OPTIONS    = 204004; 
integer SET_FONT_TEXTURE    = 204005; 
integer SET_LINE_COLOR      = 204006; 
integer SET_COLOR           = 204007; 
integer RESCAN_LINKSET      = 204008;

//internal API
integer REGISTER_SLAVE      = 205000;
integer SLAVE_RECOGNIZED    = 205001;
integer SLAVE_DISPLAY       = 205003;
integer SLAVE_DISPLAY_EXTENDED = 205004;
integer SLAVE_RESET         = 205005;

// This is an extended character escape sequence.
string  ESCAPE_SEQUENCE = "\\e";

// This is used to get an index for the extended character.
string  EXTENDED_INDEX  = "123456789abcdef";


// Face numbers. 
integer FACE_1          = 0; 
integer FACE_2          = 1; 
integer FACE_3          = 2; 
integer FACE_4          = 3; 
integer FACE_5          = 4;
integer FACE_6          = 5;
integer FACE_7          = 6;
integer FACE_8          = 7;


// Used to hide the text after a fade-out. 
key     TRANSPARENT     = "f54a0c32-3cd1-d49a-5b4f-7b792bebc204";
key     null_key        = NULL_KEY;

// This is a list of textures for all 2-character combinations. 
list    CHARACTER_GRID  = [ 
        "00e9f9f7-0669-181c-c192-7f8e67678c8d", 
        "347a5cb6-0031-7ec0-2fcf-f298eebf3c0e", 
        "4e7e689e-37f1-9eca-8596-a958bbd23963", 
        "19ea9c21-67ba-8f6f-99db-573b1b877eb1", 
        "dde7b412-cda1-652f-6fc2-73f4641f96e1", 
        "af6fa3bb-3a6c-9c4f-4bf5-d1c126c830da", 
        "a201d3a2-364b-43b6-8686-5881c0f82a94", 
        "b674dec8-fead-99e5-c28d-2db8e4c51540", 
        "366e05f3-be6b-e5cf-c33b-731dff649caa", 
        "75c4925c-0427-dc0c-c71c-e28674ff4d27", 
        "dcbe166b-6a97-efb2-fc8e-e5bc6a8b1be6", 
        "0dca2feb-fc66-a762-db85-89026a4ecd68", 
        "a0fca76f-503a-946b-9336-0a918e886f7a", 
        "67fb375d-89a1-5a4f-8c7a-0cd1c066ffc4", 
        "300470b2-da34-5470-074c-1b8464ca050c", 
        "d1f8e91c-ce2b-d85e-2120-930d3b630946", 
        "2a190e44-7b29-dadb-0bff-c31adaf5a170", 
        "75d55e71-f6f8-9835-e746-a45f189f30a1", 
        "300fac33-2b30-3da3-26bc-e2d70428ec19", 
        "0747c776-011a-53ce-13ee-8b5bb9e87c1e", 
        "85a855c3-a94f-01ca-33e0-7dde92e727e2", 
        "cbc1dab2-2d61-2986-1949-7a5235c954e1", 
        "f7aef047-f266-9596-16df-641010edd8e1", 
        "4c34ebf7-e5e1-2e1a-579f-e224d9d5e71b", 
        "4a69e98c-26a5-ad05-e92e-b5b906ad9ef9", 
        "462a9226-2a97-91ac-2d89-57ab33334b78", 
        "20b24b3a-8c57-82ee-c6ed-555003f5dbcd", 
        "9b481daa-9ea8-a9fa-1ee4-ab9a0d38e217", 
        "c231dbdc-c842-15b0-7aa6-6da14745cfdc", 
        "c97e3cbb-c9a3-45df-a0ae-955c1f4bf9cf", 
        "f1e7d030-ff80-a242-cb69-f6951d4eae3b", 
        "ed32d6c4-d733-c0f1-f242-6df1d222220d", 
        "88f96a30-dccf-9b20-31ef-da0dfeb23c72", 
        "252f2595-58b8-4bcc-6515-fa274d0cfb65", 
        "f2838c4f-de80-cced-dff8-195dfdf36b2c", 
        "cc2594fe-add2-a3df-cdb3-a61711badf53", 
        "e0ce2972-da00-955c-129e-3289b3676776", 
        "3e0d336d-321f-ddfa-5c1b-e26131766f6a", 
        "d43b1dc4-6b51-76a7-8b90-38865b82bf06", 
        "06d16cbb-1868-fd1d-5c93-eae42164a37d", 
        "dd5d98cf-273e-3fd0-f030-48be58ee3a0b", 
        "0e47c89e-de4a-6233-a2da-cb852aad1b00", 
        "fb9c4a55-0e13-495b-25c4-f0b459dc06de", 
        "e3ce8def-312c-735b-0e48-018b6799c883", 
        "2f713216-4e71-d123-03ed-9c8554710c6b", 
        "4a417d8a-1f4f-404b-9783-6672f8527911", 
        "ca5e21ec-5b20-5909-4c31-3f90d7316b33", 
        "06a4fcc3-e1c4-296d-8817-01f88fbd7367", 
        "130ac084-6f3c-95de-b5b6-d25c80703474", 
        "59d540a0-ae9d-3606-5ae0-4f2842b64cfa", 
        "8612ae9a-f53c-5bf4-2899-8174d7abc4fd", 
        "12467401-e979-2c49-34e0-6ac761542797", 
        "d53c3eaa-0404-3860-0675-3e375596c3e3", 
        "9f5b26bd-81d3-b25e-62fe-5b671d1e3e79", 
        "f57f0b64-a050-d617-ee00-c8e9e3adc9cb", 
        "beff166a-f5f3-f05e-e020-98f2b00e27ed", 
        "02278a65-94ba-6d5e-0d2b-93f2e4f4bf70", 
        "a707197d-449e-5b58-846c-0c850c61f9d6", 
        "021d4b1a-9503-a44f-ee2b-976eb5d80e68", 
        "0ae2ffae-7265-524d-cb76-c2b691992706"];
list    CHARACTER_GRID2  = [         
        "f6e41cf2-1104-bd0b-0190-dffad1bac813", 
        "2b4bb15e-956d-56ae-69f5-d26a20de0ce7", 
        "f816da2c-51f1-612a-2029-a542db7db882", 
        "345fea05-c7be-465c-409f-9dcb3bd2aa07", 
        "b3017e02-c063-5185-acd5-1ef5f9d79b89", 
        "4dcff365-1971-3c2b-d73c-77e1dc54242a" 
          ]; 

///////////// END CONSTANTS ////////////////

///////////// GLOBAL VARIABLES /////////////// 
// All displayable characters.  Default to ASCII order. 
string gCharIndex; 
// This is the channel to listen on while acting 
// as a cell in a larger display. 
integer gCellChannel      = -1; 
// This is the starting character position in the cell channel message 
// to render. 
integer gCellCharPosition = 0;
// This is whether or not to use the fade in/out special effect. 
integer gCellUseFading      = FALSE; 
// This is how long to display the text before fading out (if using 
// fading special effect). 
// Note: < 0  means don't fade out. 
float   gCellHoldDelay      = 1.0; 

integer gSlaveRegistered;
list gSlaveNames;

integer BANK_STRIDE = 3; //offset, length, highest_dirty
list gBankingData;

/////////// END GLOBAL VARIABLES //////////// 

ResetCharIndex() { 
    gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"; 
    // \" <-- Fixes LSL syntax highlighting bug. 
    gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~"; 
    gCharIndex += "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"; 
} 

vector GetGridPos(integer index1, integer index2) { 
    // There are two ways to use the lookup table... 
    integer Col; 
    integer Row; 
    if (index1 >= index2) { 
        // In this case, the row is the index of the first character: 
        Row = index1; 
        // And the col is the index of the second character (x2) 
        Col = index2 * 2; 
    } 
    else { // Index1 < Index2 
        // In this case, the row is the index of the second character: 
        Row = index2; 
        // And the col is the index of the first character, x2, offset by 1. 
        Col = index1 * 2 + 1; 
    } 
    return <Col, Row, 0>; 
} 

string GetGridTexture(vector grid_pos) { 
    // Calculate the texture in the grid to use. 
    integer GridCol = llRound(grid_pos.x) / 20; 
    integer GridRow = llRound(grid_pos.y) / 10; 

    // Lookup the texture. 
    key Texture = llList2Key(CHARACTER_GRID, GridRow * (GridRow + 1) / 2 + GridCol); 
    return Texture; 
} 

vector GetGridOffset(vector grid_pos) { 
    // Zoom in on the texture showing our character pair. 
    integer Col = llRound(grid_pos.x) % 20; 
    integer Row = llRound(grid_pos.y) % 10; 

    // Return the offset in the texture. 
    return <-0.45 + 0.05 * Col, 0.45 - 0.1 * Row, 0.0>; 
} 

ShowChars(integer link,vector grid_pos1, vector grid_pos2, vector grid_pos3, vector grid_pos4, vector grid_pos5, vector grid_pos6, vector grid_pos7, vector grid_pos8) { 
   // Set the primitive textures directly. 
     
//integer face, string name, vector repeats, vector offsets, float rotation                
   llSetLinkPrimitiveParamsFast( link , [ 
        PRIM_TEXTURE, FACE_1, GetGridTexture(grid_pos1), <0.1, 0.1, 0>, GetGridOffset(grid_pos1), 0.0, 
        PRIM_TEXTURE, FACE_2, GetGridTexture(grid_pos2), <0.1, 0.1, 0>, GetGridOffset(grid_pos2), 0.0, 
        PRIM_TEXTURE, FACE_3, GetGridTexture(grid_pos3), <0.1, 0.1, 0>, GetGridOffset(grid_pos3), 0.0, 
        PRIM_TEXTURE, FACE_4, GetGridTexture(grid_pos4), <0.1, 0.1, 0>, GetGridOffset(grid_pos4), 0.0, 
        PRIM_TEXTURE, FACE_5, GetGridTexture(grid_pos5), <0.1, 0.1, 0>, GetGridOffset(grid_pos5), 0.0,
        PRIM_TEXTURE, FACE_6, GetGridTexture(grid_pos6), <0.1, 0.1, 0>, GetGridOffset(grid_pos6), 0.0,
        PRIM_TEXTURE, FACE_7, GetGridTexture(grid_pos7), <0.1, 0.1, 0>, GetGridOffset(grid_pos7), 0.0,
        PRIM_TEXTURE, FACE_8, GetGridTexture(grid_pos8), <0.1, 0.1, 0>, GetGridOffset(grid_pos8), 0.0 
        ]); 
}

RenderString(integer link, string str) {
    // Get the grid positions for each pair of characters. 
    vector GridPos1 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)), 
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) ); 
    vector GridPos2 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)), 
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) ); 
    vector GridPos3 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)), 
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 5, 5)) ); 
    vector GridPos4 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 6, 6)), 
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 7, 7)) ); 
    vector GridPos5 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 8, 8)), 
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 9, 9)) );
    vector GridPos6 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 10, 10)), 
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 11, 11)) );
    vector GridPos7 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 12, 12)), 
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 13, 13)) );
    vector GridPos8 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 14, 14)), 
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 15, 15)) );                                   

    // Use these grid positions to display the correct textures/offsets. 
    ShowChars(link,GridPos1, GridPos2, GridPos3, GridPos4, GridPos5, GridPos6, GridPos7, GridPos8); 
}

//RenderWithEffects(integer link, string str) { 
//    // Get the grid positions for each pair of characters. 
//    vector GridPos1 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)), 
//                                  llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) ); 
//    vector GridPos2 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)), 
//                                  llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) ); 
//    vector GridPos3 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)), 
//                                  llSubStringIndex(gCharIndex, llGetSubString(str, 5, 5)) ); 
//    vector GridPos4 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 6, 6)), 
//                                  llSubStringIndex(gCharIndex, llGetSubString(str, 7, 7)) ); 
//    vector GridPos5 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 8, 8)), 
//                                  llSubStringIndex(gCharIndex, llGetSubString(str, 9, 9)) );                          //         
//
//      // First set the alpha to the lowest possible. 
//   llSetAlpha(0.05, ALL_SIDES); 
//
//    // Use these grid positions to display the correct textures/offsets. 
//    ShowChars(link,GridPos1, GridPos2, GridPos3, GridPos4, GridPos5);
//
//    float Alpha = 0.10; 
//    for (; Alpha <= 1.0; Alpha += 0.05)  
//       llSetAlpha(Alpha, ALL_SIDES); 
//          // See if we want to fade out as well. 
//   if (gCellHoldDelay < 0.0) 
//       // No, bail out. (Just keep showing the string at full strength). 
//       return; 
//          // Hold the text for a while. 
//   llSleep(gCellHoldDelay); 
//      // Now fade out. 
//   for (Alpha = 0.95; Alpha >= 0.05; Alpha -= 0.05) 
//       llSetAlpha(Alpha, ALL_SIDES); 
//          // Make the text transparent to fully hide it. 
//   llSetTexture(TRANSPARENT, ALL_SIDES); 
//} 

integer RenderExtended(integer link, string str,integer render) {
    // Look for escape sequences.
    integer length = 0;
    list Parsed       = llParseString2List(str, [], (list)ESCAPE_SEQUENCE);
    integer ParsedLen = llGetListLength(Parsed);

    // Create a list of index values to work with.
    list Indices;
    // We start with room for 6 indices.
    integer IndicesLeft = 16;

    string Token;
    integer Clipped;
    integer LastWasEscapeSequence = FALSE;
    // Work from left to right.
    integer i=0;
    for (; i < ParsedLen && IndicesLeft > 0; ++i) {
        Token = llList2String(Parsed, i);

        // If this is an escape sequence, just set the flag and move on.
        if (Token == ESCAPE_SEQUENCE) {
            LastWasEscapeSequence = TRUE;
        }
        else { // Token != ESCAPE_SEQUENCE
            // Otherwise this is a normal token.  Check its length.
            Clipped = FALSE;
            integer TokenLength = llStringLength(Token);
            // Clip if necessary.
            if (TokenLength > IndicesLeft) {
                TokenLength = llStringLength(Token = llGetSubString(Token, 0, IndicesLeft - 1));
                IndicesLeft = 0;
                Clipped = TRUE;
            }
            else
                IndicesLeft -= TokenLength;

            // Was the previous token an escape sequence?
            if (LastWasEscapeSequence) {
                // Yes, the first character is an escape character, the rest are normal.
                length += 2 + TokenLength;
                if (render)
                {
                    // This is the extended character.
                    Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
                    
                    // These are the normal characters.
                    integer j=1;
                    for (; j < TokenLength; ++j)
                    {
                        Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
                    }
                }
            }
            else { // Normal string.
                // Just add the characters normally.
                length += TokenLength;
                if(render)
                {
                    integer j=0;
                    for (; j < TokenLength; ++j)
                    {
                        Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
                    }
                }
            }

            // Unset this flag, since this was not an escape sequence.
            LastWasEscapeSequence = FALSE;
        }
    }

    if(render)
    {
        // Use the indices to create grid positions.
        vector GridPos1 = GetGridPos( llList2Integer(Indices, 0), llList2Integer(Indices, 1) );
        vector GridPos2 = GetGridPos( llList2Integer(Indices, 2), llList2Integer(Indices, 3) );
        vector GridPos3 = GetGridPos( llList2Integer(Indices, 4), llList2Integer(Indices, 5) );
        vector GridPos4 = GetGridPos( llList2Integer(Indices, 6), llList2Integer(Indices, 7) );
        vector GridPos5 = GetGridPos( llList2Integer(Indices, 8), llList2Integer(Indices, 9) );
        vector GridPos6 = GetGridPos( llList2Integer(Indices, 10), llList2Integer(Indices, 11) );
        vector GridPos7 = GetGridPos( llList2Integer(Indices, 12), llList2Integer(Indices, 13) );
        vector GridPos8 = GetGridPos( llList2Integer(Indices, 14), llList2Integer(Indices, 15) );
        
        // Use these grid positions to display the correct textures/offsets.
        ShowChars(link, GridPos1, GridPos2, GridPos3, GridPos4, GridPos5, GridPos6, GridPos7, GridPos8);
    }
    return length;
}


integer ConvertIndex(integer index) {
    // This converts from an ASCII based index to our indexing scheme.
    if (index >= 32) // ' ' or higher
        index -= 32;
    else { // index < 32
        // Quick bounds check.
        if (index > 15)
            index = 15;

        index += 94; // extended characters
    }

    return index;
}


PassToRender(integer render,string message, integer bank)
{
    float time;
    integer extendedlen = 0;
    integer link;
    
    integer i = 0;
    integer msgLen = llStringLength(message);
    string TextToRender;
    integer num_slaves=llGetListLength(gSlaveNames);
    string slave_name; //avoids unnecessary casts, keeping it as a string
    

    //get the bank offset and length
    integer bank_offset=llList2Integer(gBankingData, (bank * BANK_STRIDE));
    integer bank_length=llList2Integer(gBankingData, (bank * BANK_STRIDE) + 1);
    integer bank_highest_dirty=llList2Integer(gBankingData, (bank * BANK_STRIDE) + 2);

    integer x=0;    
    for (;x < msgLen;x = x + 16)
    {

        if (i >= bank_length)  //we don't want to run off the end of the bank
        {
            //set the dirty to max, and bail out, we're done
            gBankingData=llListReplaceList(gBankingData, [bank_length], (bank * BANK_STRIDE) + 2, (bank * BANK_STRIDE) + 2);
            return;
        }   
        
        link = unpack(gXyTextPrims,(i + bank_offset));
        TextToRender = llGetSubString(message, x, x + 32);
        
        if(gSlaveRegistered && (link % (num_slaves +1)))
        {
            slave_name=llList2String(gSlaveNames, (link % (num_slaves + 1)) - 1);
            if (render == 1)
                llMessageLinked(LINK_THIS, SLAVE_DISPLAY, TextToRender, (key)((string)link + "," + slave_name));
            if (render == 2)
            {
                if(llSubStringIndex(TextToRender,"\e")>x+16)
                {
                    extendedlen = 16;
                }
                else
                {
                    extendedlen = RenderExtended(link,TextToRender,0);
                }

                if(extendedlen>16)
                {
                    x += extendedlen-16;
                }

                llMessageLinked(LINK_THIS,SLAVE_DISPLAY_EXTENDED,TextToRender,(key)((string)link+","+slave_name));
            }        
        }
        else
        {
            if (render == 1)
                RenderString(link,TextToRender);
            if (render == 2)
            {
                extendedlen = RenderExtended(link,TextToRender,1);
                if(extendedlen>16)
                {
                    x += extendedlen-16;
                }
            }
            
//            if (render == 3)
//                RenderWithEffects(link,TextToRender);
        }
        ++i;            
    }
    
    if (bank_highest_dirty==0)
        bank_highest_dirty=bank_length;
    
    integer current_highest_dirty=i;
    while (i < bank_highest_dirty)
    {
        link = unpack(gXyTextPrims,(i + bank_offset));
        
        if(gSlaveRegistered && (link % (num_slaves+1) != 0))
        {
            slave_name=llList2String(gSlaveNames, (link % (num_slaves + 1)) - 1);
            llMessageLinked(LINK_THIS, SLAVE_DISPLAY, "     ", (key)((string)link + "," + slave_name));       
            //sorry, no fade effect with slave
        }
        else
        {
            RenderString(link,"          ");
        }
        ++i;        
    }
    gBankingData=llListReplaceList(gBankingData, [current_highest_dirty], (bank * BANK_STRIDE) + 2, (bank * BANK_STRIDE) + 2);
}

// Bitwise Voodoo by Gigs Taggart and optimized by Strife Onizuka
list gXyTextPrims;


integer get_number_of_prims()
{//ignores avatars.
    integer a = llGetNumberOfPrims();
    while(llGetAgentSize(llGetLinkKey(a)))
        --a;
    return a;
}

//functions to pack 8-bit shorts into ints
list pack_and_insert(list in_list, integer pos, integer value)
{
//    //figure out the bitpack position
//    integer pack = pos & 3; //4 bytes per int
//    pos=pos >> 2;
//    integer shifted = value << (pack << 3);
//    integer old_value = llList2Integer(in_list, pos);
//    shifted = old_value | shifted;
//    in_list = llListReplaceList(in_list, (list)shifted, pos, pos);
//    return in_list;
    //Safe optimized version
    integer index = pos >> 2;
    return llListReplaceList(in_list, (list)(llList2Integer(in_list, index) | (value << ((pos & 3) << 3))), index, index);
}

integer unpack(list in_list, integer pos)
{
    return (llList2Integer(in_list, pos >> 2) >> ((pos & 3) << 3)) & 0x000000FF;//unsigned
//    return (llList2Integer(in_list, pos >> 2) << (((~pos) & 3) << 3)) >> 24;//signed
}


change_color(vector color)
{
    integer num_prims=llGetListLength(gXyTextPrims) << 2;
    
    integer i = 0;
    
    for (; i<=num_prims; ++i)
    {
        integer link = unpack(gXyTextPrims,i);
        if (!link)
            return;
        
        llSetLinkPrimitiveParamsFast( link,[ 
            PRIM_COLOR, FACE_1, color, 1.0,
            PRIM_COLOR, FACE_2, color, 1.0,
            PRIM_COLOR, FACE_3, color, 1.0,
            PRIM_COLOR, FACE_4, color, 1.0,
            PRIM_COLOR, FACE_5, color, 1.0,
            PRIM_COLOR, FACE_6, color, 1.0,
            PRIM_COLOR, FACE_7, color, 1.0,
            PRIM_COLOR, FACE_8, color, 1.0            
        ]);
    }
}

change_line_color(integer bank, vector color)
{    

    //get the bank offset and length
    integer i = llList2Integer(gBankingData, (bank * BANK_STRIDE));
    integer bank_end = i + llList2Integer(gBankingData, (bank * BANK_STRIDE) + 1);

    for (; i < bank_end; ++i)
    {     
        integer link = unpack(gXyTextPrims,i);
        if (!link)
            return;
        
        llSetLinkPrimitiveParamsFast( link,[ 
            PRIM_COLOR, FACE_1, color, 1.0,
            PRIM_COLOR, FACE_2, color, 1.0,
            PRIM_COLOR, FACE_3, color, 1.0,
            PRIM_COLOR, FACE_4, color, 1.0,
            PRIM_COLOR, FACE_5, color, 1.0,
            PRIM_COLOR, FACE_6, color, 1.0,
            PRIM_COLOR, FACE_7, color, 1.0,
            PRIM_COLOR, FACE_8, color, 1.0
        ]);
    }
}

init()
{
    integer num_prims=get_number_of_prims();

    string link_name;
    integer bank=0;
    integer bank_empty=FALSE;
    integer prims_pointer=0; //"pointer" to the next entry to be used in the gXyTextPrims list.
    
    list temp_bank=[];
    integer temp_bank_stride=2;
    
   
    gXyTextPrims=[];
    integer x=0;
    for (;x<64;++x)
    {
        gXyTextPrims= (gXyTextPrims = []) + gXyTextPrims + [0];  //we need to pad out the list to make it easier to add things in any order later
    }
    
    @loop;
    
    {
       
        //loop over all prims, looking for ones in the current bank
        for(x=0;x<=num_prims;++x)
        {
            link_name=llGetLinkName(x);
            list tmp = llParseString2List(link_name, ["-"], []);
            if(llList2String(tmp,0)== "xyzzytext")
            {
                integer prims_bank=llList2Integer(tmp,1);
                if (llList2Integer(tmp,1)==bank)
                {
                    temp_bank+=llList2Integer(tmp,2) + (list)x;
                }
            }
            
        }

    
        if (temp_bank!=[])
        {
            //sort the current bank
            temp_bank=llListSort(temp_bank, temp_bank_stride, TRUE);
            
            integer temp_len=llGetListLength(temp_bank);
            
            //store metadata
            gBankingData+=[prims_pointer,temp_len/temp_bank_stride,0];
            
            //repack the bank into the prim list
            for (x=0; x < temp_len; x+=temp_bank_stride)
            {
                gXyTextPrims = pack_and_insert(gXyTextPrims, prims_pointer, llList2Integer(temp_bank, x+1));
                ++prims_pointer;
            }
            ++bank;
            temp_bank=[];
            jump loop;
        }
    }
    
    llMessageLinked(LINK_THIS, SLAVE_RESET, "" , null_key);
}

default { 
    state_entry() { 
        // Initialize the character index. 
        ResetCharIndex();
        init();
    } 

   on_rez(integer num)
   {
      llResetScript();       
   }
   
    link_message(integer sender, integer channel, string data, key id) {
        if(id == null_key)
            id="0";
        
        if (channel == DISPLAY_STRING) { 
            PassToRender(1,data, (integer)((string)id)); 
            return; 
        } 
        else if (channel == DISPLAY_EXTENDED) { 
             PassToRender(2,data, (integer)((string)id)); 
            return; 
        }
        else if (channel == REMAP_INDICES) {
            // Parse the message, splitting it up into index values.
            list Parsed = llCSV2List(data);
            integer i;
            // Go through the list and swap each pair of indices.
            for (i = 0; i < llGetListLength(Parsed); i += 2) {
                integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
                integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );

                // Swap these index values.
                string Value1 = llGetSubString(gCharIndex, Index1, Index1);
                string Value2 = llGetSubString(gCharIndex, Index2, Index2);

                gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
                gCharIndex = llInsertString(gCharIndex, Index1, Value2);

                gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
                gCharIndex = llInsertString(gCharIndex, Index2, Value1);
            }
            return;
        }
        else if (channel == RESET_INDICES) {
            // Restore the character index back to default settings.
            ResetCharIndex();
            return;
        }        
        else if (channel == RESCAN_LINKSET)
        {
            init();
        }
        else if (channel == SET_COLOR) {
            change_color((vector)data); 
        }
        else if (channel == SET_LINE_COLOR) {
            change_line_color((integer)((string)id), (vector)data); 
        }     
        else if (channel == REGISTER_SLAVE)
        {
            if(!~llListFindList(gSlaveNames, (list)data))
            {//isn't registered yet
                gSlaveNames += data;
                gSlaveRegistered=TRUE;
                //llOwnerSay((string)llGetListLength(gSlaveNames) + " Slave(s) Recognized: " + data);
            }
//            else
//            {//it already exists
//                llOwnerSay((string)llGetListLength(gSlaveNames) + " Slave, Existing Slave Recognized: " + data);
//            }
            llMessageLinked(LINK_THIS, SLAVE_RECOGNIZED, data , null_key);
        }
    }

    changed(integer change)
    {
        if(change&CHANGED_INVENTORY)
        {
            if(gSlaveRegistered)        
            {
                //by using negative indexes they don't need to be adjusted when an entry is deleted.
                integer x = ~llGetListLength(gSlaveNames);
                while(++x)
                {
                    if (!~llGetInventoryType(llList2String(gSlaveNames, x)))
                    {
                        //llOwnerSay("Slave Removed: " + llList2String(gSlaveNames, x));
                        gSlaveNames = llDeleteSubList(gSlaveNames, x, x);
                    }
                }
                gSlaveRegistered = !(gSlaveNames == []);
            }
        }
    }
}

Slave Script

////////////////////////////////////////////
// XyzzyText v2.1m (16-Char MESH) by Traven Sachs 29-November-2012
// XyzzyText v2.1(10-Char) by Thraxis Epsilon
// XyText v2.0 SLAVE Script (5 Face, Single Texture) 
//
// Heavily Modified by Thraxis Epsilon, Gigs Taggart 5/2007 and Strife Onizuka 8/2007
// Rewrite to allow one-script-per-object operation w/ optional slaves
// Enable prim-label functionality
// Enabled Banking
// Enabled 10-char per prim
// Enabled 16-Char on an 8 Face Mesh Plane Prim
//
// Modified by Kermitt Quirk 19/01/2006 
// To add support for 5 face prim instead of 3 
// 
// Originally Written by Xylor Baysklef 
// 
//
//////////////////////////////////////////// 


integer REMAP_INDICES       = 204002; 
integer RESET_INDICES       = 204003;

//internal API
integer REGISTER_SLAVE      = 205000;
integer SLAVE_RECOGNIZED    = 205001;
integer SLAVE_DISPLAY       = 205003;
integer SET_FONT_TEXTURE    = 204005; 

integer SLAVE_DISPLAY_EXTENDED = 205004;
integer SLAVE_RESET = 205005;

// This is an extended character escape sequence.
string  ESCAPE_SEQUENCE = "\\e";

// This is used to get an index for the extended character.
string  EXTENDED_INDEX  = "123456789abcdef";

// Face numbers. 
integer FACE_1          = 0; 
integer FACE_2          = 1; 
integer FACE_3          = 2; 
integer FACE_4          = 3; 
integer FACE_5          = 4;
integer FACE_6          = 5;
integer FACE_7          = 6;
integer FACE_8          = 7;

// This is a list of textures for all 2-character combinations. 
list    CHARACTER_GRID  = [ 
        "00e9f9f7-0669-181c-c192-7f8e67678c8d", 
        "347a5cb6-0031-7ec0-2fcf-f298eebf3c0e", 
        "4e7e689e-37f1-9eca-8596-a958bbd23963", 
        "19ea9c21-67ba-8f6f-99db-573b1b877eb1", 
        "dde7b412-cda1-652f-6fc2-73f4641f96e1", 
        "af6fa3bb-3a6c-9c4f-4bf5-d1c126c830da", 
        "a201d3a2-364b-43b6-8686-5881c0f82a94", 
        "b674dec8-fead-99e5-c28d-2db8e4c51540", 
        "366e05f3-be6b-e5cf-c33b-731dff649caa", 
        "75c4925c-0427-dc0c-c71c-e28674ff4d27", 
        "dcbe166b-6a97-efb2-fc8e-e5bc6a8b1be6", 
        "0dca2feb-fc66-a762-db85-89026a4ecd68", 
        "a0fca76f-503a-946b-9336-0a918e886f7a", 
        "67fb375d-89a1-5a4f-8c7a-0cd1c066ffc4", 
        "300470b2-da34-5470-074c-1b8464ca050c", 
        "d1f8e91c-ce2b-d85e-2120-930d3b630946", 
        "2a190e44-7b29-dadb-0bff-c31adaf5a170", 
        "75d55e71-f6f8-9835-e746-a45f189f30a1", 
        "300fac33-2b30-3da3-26bc-e2d70428ec19", 
        "0747c776-011a-53ce-13ee-8b5bb9e87c1e", 
        "85a855c3-a94f-01ca-33e0-7dde92e727e2", 
        "cbc1dab2-2d61-2986-1949-7a5235c954e1", 
        "f7aef047-f266-9596-16df-641010edd8e1", 
        "4c34ebf7-e5e1-2e1a-579f-e224d9d5e71b", 
        "4a69e98c-26a5-ad05-e92e-b5b906ad9ef9", 
        "462a9226-2a97-91ac-2d89-57ab33334b78", 
        "20b24b3a-8c57-82ee-c6ed-555003f5dbcd", 
        "9b481daa-9ea8-a9fa-1ee4-ab9a0d38e217", 
        "c231dbdc-c842-15b0-7aa6-6da14745cfdc", 
        "c97e3cbb-c9a3-45df-a0ae-955c1f4bf9cf", 
        "f1e7d030-ff80-a242-cb69-f6951d4eae3b", 
        "ed32d6c4-d733-c0f1-f242-6df1d222220d", 
        "88f96a30-dccf-9b20-31ef-da0dfeb23c72", 
        "252f2595-58b8-4bcc-6515-fa274d0cfb65", 
        "f2838c4f-de80-cced-dff8-195dfdf36b2c", 
        "cc2594fe-add2-a3df-cdb3-a61711badf53", 
        "e0ce2972-da00-955c-129e-3289b3676776", 
        "3e0d336d-321f-ddfa-5c1b-e26131766f6a", 
        "d43b1dc4-6b51-76a7-8b90-38865b82bf06", 
        "06d16cbb-1868-fd1d-5c93-eae42164a37d", 
        "dd5d98cf-273e-3fd0-f030-48be58ee3a0b", 
        "0e47c89e-de4a-6233-a2da-cb852aad1b00", 
        "fb9c4a55-0e13-495b-25c4-f0b459dc06de", 
        "e3ce8def-312c-735b-0e48-018b6799c883", 
        "2f713216-4e71-d123-03ed-9c8554710c6b", 
        "4a417d8a-1f4f-404b-9783-6672f8527911", 
        "ca5e21ec-5b20-5909-4c31-3f90d7316b33", 
        "06a4fcc3-e1c4-296d-8817-01f88fbd7367", 
        "130ac084-6f3c-95de-b5b6-d25c80703474", 
        "59d540a0-ae9d-3606-5ae0-4f2842b64cfa", 
        "8612ae9a-f53c-5bf4-2899-8174d7abc4fd", 
        "12467401-e979-2c49-34e0-6ac761542797", 
        "d53c3eaa-0404-3860-0675-3e375596c3e3", 
        "9f5b26bd-81d3-b25e-62fe-5b671d1e3e79", 
        "f57f0b64-a050-d617-ee00-c8e9e3adc9cb", 
        "beff166a-f5f3-f05e-e020-98f2b00e27ed", 
        "02278a65-94ba-6d5e-0d2b-93f2e4f4bf70", 
        "a707197d-449e-5b58-846c-0c850c61f9d6", 
        "021d4b1a-9503-a44f-ee2b-976eb5d80e68", 
        "0ae2ffae-7265-524d-cb76-c2b691992706", 
        "f6e41cf2-1104-bd0b-0190-dffad1bac813", 
        "2b4bb15e-956d-56ae-69f5-d26a20de0ce7", 
        "f816da2c-51f1-612a-2029-a542db7db882", 
        "345fea05-c7be-465c-409f-9dcb3bd2aa07"];
list    CHARACTER_GRID2  = [         
        "b3017e02-c063-5185-acd5-1ef5f9d79b89", 
        "4dcff365-1971-3c2b-d73c-77e1dc54242a" 
          ]; 

///////////// END CONSTANTS ////////////////
string gCharIndex; 

integer gActive; //if we are recognized, this is true
/////////// END GLOBAL VARIABLES ////////////
ResetCharIndex() { 
    gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"; 
    // \" <-- Fixes LSL syntax highlighting bug. 
    gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~"; 
    gCharIndex += "\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"; 
} 

vector GetGridPos(integer index1, integer index2) { 
    // There are two ways to use the lookup table... 
    integer Col; 
    integer Row; 
    if (index1 >= index2) { 
        // In this case, the row is the index of the first character: 
        Row = index1; 
        // And the col is the index of the second character (x2) 
        Col = index2 * 2; 
    } 
    else { // Index1 < Index2 
        // In this case, the row is the index of the second character: 
        Row = index2; 
        // And the col is the index of the first character, x2, offset by 1. 
        Col = index1 * 2 + 1; 
    } 
    return <Col, Row, 0>; 
} 

string GetGridTexture(vector grid_pos) { 
    // Calculate the texture in the grid to use. 
    integer GridCol = llRound(grid_pos.x) / 20; 
    integer GridRow = llRound(grid_pos.y) / 10; 

    // Lookup the texture. 
    key Texture = llList2Key(CHARACTER_GRID, GridRow * (GridRow + 1) / 2 + GridCol); 
    return Texture; 
} 

vector GetGridOffset(vector grid_pos) { 
    // Zoom in on the texture showing our character pair. 
    integer Col = llRound(grid_pos.x) % 20; 
    integer Row = llRound(grid_pos.y) % 10; 

    // Return the offset in the texture. 
    return <-0.45 + 0.05 * Col, 0.45 - 0.1 * Row, 0.0>; 
} 

ShowChars(integer link,vector grid_pos1, vector grid_pos2, vector grid_pos3, vector grid_pos4, vector grid_pos5, vector grid_pos6, vector grid_pos7, vector grid_pos8) { 
   // Set the primitive textures directly. 
     
               
   llSetLinkPrimitiveParamsFast( link , [ 
        PRIM_TEXTURE, FACE_1, GetGridTexture(grid_pos1), <0.1, 0.1, 0>, GetGridOffset(grid_pos1), 0.0, 
        PRIM_TEXTURE, FACE_2, GetGridTexture(grid_pos2), <0.1, 0.1, 0>, GetGridOffset(grid_pos2), 0.0, 
        PRIM_TEXTURE, FACE_3, GetGridTexture(grid_pos3), <0.1, 0.1, 0>, GetGridOffset(grid_pos3), 0.0, 
        PRIM_TEXTURE, FACE_4, GetGridTexture(grid_pos4), <0.1, 0.1, 0>, GetGridOffset(grid_pos4), 0.0, 
        PRIM_TEXTURE, FACE_5, GetGridTexture(grid_pos5), <0.1, 0.1, 0>, GetGridOffset(grid_pos5), 0.0,
        PRIM_TEXTURE, FACE_6, GetGridTexture(grid_pos6), <0.1, 0.1, 0>, GetGridOffset(grid_pos6), 0.0,
        PRIM_TEXTURE, FACE_7, GetGridTexture(grid_pos7), <0.1, 0.1, 0>, GetGridOffset(grid_pos7), 0.0,
        PRIM_TEXTURE, FACE_8, GetGridTexture(grid_pos8), <0.1, 0.1, 0>, GetGridOffset(grid_pos8), 0.0
        ]); 
}

RenderString(integer link, string str) {
    // Get the grid positions for each pair of characters. 
    vector GridPos1 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 0, 0)), 
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 1, 1)) ); 
    vector GridPos2 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 2, 2)), 
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 3, 3)) ); 
    vector GridPos3 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 4, 4)), 
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 5, 5)) ); 
    vector GridPos4 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 6, 6)), 
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 7, 7)) ); 
    vector GridPos5 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 8, 8)), 
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 9, 9)) );
    vector GridPos6 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 10, 10)), 
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 11, 11)) );
    vector GridPos7 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 12, 12)), 
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 13, 13)) );
    vector GridPos8 = GetGridPos( llSubStringIndex(gCharIndex, llGetSubString(str, 14, 14)), 
                                  llSubStringIndex(gCharIndex, llGetSubString(str, 15, 15)) );                                   

    // Use these grid positions to display the correct textures/offsets. 
    ShowChars(link,GridPos1, GridPos2, GridPos3, GridPos4, GridPos5, GridPos6, GridPos7, GridPos8); 
}

RenderExtended(integer link, string str) {
    // Look for escape sequences.
    list Parsed       = llParseString2List(str, [], (list)ESCAPE_SEQUENCE);
    integer ParsedLen = llGetListLength(Parsed);

    // Create a list of index values to work with.
    list Indices;
    // We start with room for 16 indices.
    integer IndicesLeft = 16;

    string Token;
    integer Clipped;
    integer LastWasEscapeSequence = FALSE;
    // Work from left to right.
    integer i=0;
    for (; i < ParsedLen && IndicesLeft > 0; ++i) {
        Token = llList2String(Parsed, i);

        // If this is an escape sequence, just set the flag and move on.
        if (Token == ESCAPE_SEQUENCE) {
            LastWasEscapeSequence = TRUE;
        }
        else { // Token != ESCAPE_SEQUENCE
            // Otherwise this is a normal token.  Check its length.
            Clipped = FALSE;
            integer TokenLength = llStringLength(Token);
            // Clip if necessary.
            if (TokenLength > IndicesLeft) {
                TokenLength = llStringLength(Token = llGetSubString(Token, 0, IndicesLeft - 1));
                IndicesLeft = 0;
                Clipped = TRUE;
            }
            else
                IndicesLeft -= TokenLength;

            // Was the previous token an escape sequence?
            if (LastWasEscapeSequence) {
                // Yes, the first character is an escape character, the rest are normal.
                // This is the extended character.
                Indices += [llSubStringIndex(EXTENDED_INDEX, llGetSubString(Token, 0, 0)) + 95];
                
                // These are the normal characters.
                integer j=1;
                for (; j < TokenLength; ++j)
                {
                    Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
                }
            }
            else { // Normal string.
                // Just add the characters normally.
                integer j=0;
                for (; j < TokenLength; ++j)
                {
                    Indices += [llSubStringIndex(gCharIndex, llGetSubString(Token, j, j))];
                }
            }

            // Unset this flag, since this was not an escape sequence.
            LastWasEscapeSequence = FALSE;
        }
    }

    // Use the indices to create grid positions.
    vector GridPos1 = GetGridPos( llList2Integer(Indices, 0), llList2Integer(Indices, 1) );
    vector GridPos2 = GetGridPos( llList2Integer(Indices, 2), llList2Integer(Indices, 3) );
    vector GridPos3 = GetGridPos( llList2Integer(Indices, 4), llList2Integer(Indices, 5) );
    vector GridPos4 = GetGridPos( llList2Integer(Indices, 6), llList2Integer(Indices, 7) );
    vector GridPos5 = GetGridPos( llList2Integer(Indices, 8), llList2Integer(Indices, 9) );
    vector GridPos6 = GetGridPos( llList2Integer(Indices, 10), llList2Integer(Indices, 11) );
    vector GridPos7 = GetGridPos( llList2Integer(Indices, 12), llList2Integer(Indices, 13) );
    vector GridPos8 = GetGridPos( llList2Integer(Indices, 14), llList2Integer(Indices, 15) );
    
    // Use these grid positions to display the correct textures/offsets.
    ShowChars(link,GridPos1, GridPos2, GridPos3, GridPos4, GridPos5, GridPos6, GridPos7, GridPos8);
}

integer ConvertIndex(integer index) {
    // This converts from an ASCII based index to our indexing scheme.
    if (index >= 32) // ' ' or higher
        index -= 32;
    else { // index < 32
        // Quick bounds check.
        if (index > 15)
            index = 15;

        index += 94; // extended characters
    }

    return index;
}


default { 
    state_entry() 
    { 
        // Initialize the character index. 
        ResetCharIndex();
        llMessageLinked(LINK_THIS, REGISTER_SLAVE, llGetScriptName() , NULL_KEY);  
    } 

   on_rez(integer num)
   {
      llResetScript();       
   }
   
    link_message(integer sender, integer channel, string data, key id) { 
        if (channel == SLAVE_RECOGNIZED)
        {
            if (data == llGetScriptName())
            {
                gActive=TRUE;
            }
            return;
        }
    
        else if (channel == SLAVE_DISPLAY) 
        { 
            if (!gActive)  
                return;
                
            list params=llCSV2List((string)id);
            if (llList2String(params, 1) != llGetScriptName())
                return;
            
            
            RenderString(llList2Integer(params, 0),data);
            return; 
        } 
    
       else if (channel == SLAVE_DISPLAY_EXTENDED) 
       {
            if (!gActive)  
                return;
                
            list params=llCSV2List((string)id);
            if (llList2String(params, 1) != llGetScriptName())
                return;
                
            RenderExtended(llList2Integer(params, 0),data);
    }
        else if (channel == REMAP_INDICES) {
            // Parse the message, splitting it up into index values.
            list Parsed = llCSV2List(data);
            integer i;
            // Go through the list and swap each pair of indices.
            for (i = 0; i < llGetListLength(Parsed); i += 2) {
                integer Index1 = ConvertIndex( llList2Integer(Parsed, i) );
                integer Index2 = ConvertIndex( llList2Integer(Parsed, i + 1) );

                // Swap these index values.
                string Value1 = llGetSubString(gCharIndex, Index1, Index1);
                string Value2 = llGetSubString(gCharIndex, Index2, Index2);

                gCharIndex = llDeleteSubString(gCharIndex, Index1, Index1);
                gCharIndex = llInsertString(gCharIndex, Index1, Value2);

                gCharIndex = llDeleteSubString(gCharIndex, Index2, Index2);
                gCharIndex = llInsertString(gCharIndex, Index2, Value1);
            }
            return;
        }
        else if (channel == RESET_INDICES) {
            // Restore the character index back to default settings.
            ResetCharIndex();
            return;
        }                 
       else if (channel == SLAVE_RESET)
        {
            ResetCharIndex();
            gActive=FALSE;
            llMessageLinked(LINK_THIS, REGISTER_SLAVE, llGetScriptName() , NULL_KEY);
        }
    }    
}

16-Char MESH Prim Setup

//There really isn't a Prim Setup for these 
//You can get a full perm 8 face plane mesh here for L$10
//https://marketplace.secondlife.com/p/Mesh-8-Faces-8x1/3014457 [1]
// Or for completely FREE here
//https://marketplace.secondlife.com/p/FREE-8-faced-mesh-prim-xytext-v2-16-characters-per-half-a-prim/3312172 [2]

8x1, high LOD, land impact 0.5: https://marketplace.secondlife.com/p/Free-full-perm-8-face-mesh-panel/4172861

https://marketplace.secondlife.com/p/Mesh-8-Faces-8x1/3014457

https://marketplace.secondlife.com/p/FREE-8-faced-mesh-prim-xytext-v2-16-characters-per-half-a-prim/3312172