XyzzyText-UTF8

From Second Life Wiki
Revision as of 19:16, 28 June 2008 by Bobbyb30 Zohari (talk | contribs) (Added <lsl> tags)
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

Xyzzy doesn't work with European characters.. :( See also ZZText for a comprehensive International low-prim solution.

However here is a Photoshop script and a variation on the main server you can use to adapt it to your needs if you are using UTF-8 fonts:

The theory is the following:

  • prepare a 512x1024 texture with in the first part the standard Xyzzy chars, and in 2nd part customized characters (you can define up to 100 extra chars)
  • modify the master and slave scripts to decode UTF-8 chars as explained below
  • vertical parameters taken from Xyzzy standard text have been modified to allow the fact we have twice the characters in vertical size.
  • typographical quality of the rendered characters is the same as the 512x512 texture version, since we are just expanding it, not compressing.

Very Important:

  • The master script is very very tight on space, so if you really want to use this you have to cut much of it, in my real application I simply removed the extended verbs and the fading options, and rewrote the UTF-8 list using the x=(x=[])+x+newlist well known trick to save memory. If you don't do this you will likely get stack-heap collision.


Here published by Salahzar Stenvaag under the MIT license.

Photoshop VBS script

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(18)
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{|}~·ΘΩ√ "    

rows(11)="Çüéâäàåçêë"
rows(12)="èïîìÄÅÉæÆ◄"
rows(13)="öòûùÿÖÜ¢£¥"
rows(14)="₧ƒáíóúñѪº"
rows(15)="¿⌐¬½¼¡«»αß"
rows(16)="ΓπΣσµτΦΘΩδ"
rows(17)="∞φε∩≡±≥≤⌠⌡"
rows(18)="÷≈°∙·√ⁿ²€ "


dim fontname
fontname="Comics"

Set docRef = appRef.Documents.Add(512, 1024, 72, "CP850 " & 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 18
   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

Instructions on how to generate the texture are already published on Xyzzy main page, but just to be sure I repeat them here:

  • openup photoshop
  • click the vbs script in explorer and it will run over your photoshop
  • add one temporary layer and fill it with black just to see your characters
  • select one text layer and use "select similar layers"
  • change your font, size etc from the text tool and look how they appear (NOTE: should be monospace font)
  • merge all the selected layer into one single one
  • go to channel tab in layers, and add one new (alpha channel) it will be black per default
  • go to your font normal layer and select all
  • go to your alpha channel and paste it down
  • be sure to have the temporary black layer deselected and to select RGB layers so to see transparency in red
  • save as tga 32 bit and upload it to SL.


Modification on the master script

Modifications are marked by // SALAHZAR <lsl> //////////////////////////////////////////// // XyzzyText v2.1.UTF8 (UTF8-support) by Salahzar Stenvaag // 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 // // 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"; // 48 pixel font key gFontTexture = "f226766c-c5ac-690e-9018-5a37367ae95a"; // 38 pixel font //key gFontTexture= "ac955f98-74bb-290f-7eb6-dca54e5e4491"; key gFontTexture= "e5efeead-c69e-eb81-e7bd-dad2bb787d2b"; // BitStream Vera Monotype // SALAHZAR

// All displayable characters. Default to ASCII order. string gCharIndex; list decode=[]; // to handle special characters from CP850 page for european countries // SALAHZAR

// 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{|}~\n\n\n\n\n";
    // special UTF-8 chars for European languages // SALAHZAR special chars according to a selection from CP850
    // these 80 chars correspond to the following chars in CP850 codepage: (some are not viewable in editor)
    // rows(11)="Çüéâäàåçêë"
    // rows(12)="èïîìÄÅÉæÆ◄"
    // rows(13)="öòûùÿÖÜ¢£¥"
    // rows(14)="₧ƒáíóúñѪº"
    // rows(15)="¿⌐¬½¼¡«»αß"
    // rows(16)="ΓπΣσµτΦΘΩδ"
    // rows(17)="∞φε∩≡±≥≤⌠⌡"
    // rows(18)="÷≈°∙·√ⁿ²€ "     
    decode= [ "%C3%87", "%C3%BC", "%C3%A9", "%C3%A2", "%C3%A4", "%C3%A0", "%C3%A5", "%C3%A7", "%C3%AA", "%C3%AB" ];
    decode+=[ "%C3%A8", "%C3%AF", "%C3%AE", "%C3%AC", "%C3%84", "%C3%85", "%C3%89", "%C3%A6", "%C3%AE", "xxxxxx" ];
    decode+=[ "%C3%B6", "%C3%B2", "%C3%BB", "%C3%B9", "%C3%BF", "%C3%96", "%C3%9C", "%C2%A2", "%C2%A3", "%C2%A5" ];
    decode+=[ "%E2%82%A7", "%C6%92", "%C3%A1", "%C3%AD", "%C3%B3", "%C3%BA", "%C3%B1", "%C3%91", "%C2%AA", "%C2%BA"];
    decode+=[ "%C2%BF", "%E2%8C%90", "%C2%AC", "%C2%BD", "%C2%BC", "%C2%A1", "%C2%AB", "%C2%BB", "%CE%B1", "%C3%9F" ];
    decode+=[ "%CE%93", "%CF%80", "%CE%A3", "%CF%83", "%C2%B5", "%CF%84", "%CE%A6", "%CE%98", "%CE%A9", "%CE%B4" ];
    decode+=[ "%E2%88%9E", "%CF%86", "%CE%B5", "%E2%88%A9", "%E2%89%A1", "%C2%B1", "%E2%89%A5", "%E2%89%A4", "%E2%8C%A0", "%E2%8C%A1" ];
    decode+=[ "%C3%B7", "%E2%89%88", "%C2%B0", "%E2%88%99", "%C2%B7", "%E2%88%9A", "%E2%81%BF", "%C2%B2", "%E2%82%AC", "" ];
    // END // SALAHZAR


}

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>; 
  return <-0.45 + 0.1 * Col, 0.425 - 0.05 * Row, 0.0>; // SALAHZAR modified vertical offsets for 512x1024 textures

}

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> 
   

// SALAHZAR modified .1 to .05 to handle different sized texture

  llSetLinkPrimitiveParams( link,[ 
       PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.126, 0.05, 0>, grid_offset1 + <0.037, 0, 0>, 0.0, 
       PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.05, 0>, grid_offset2, 0.0, 
       PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.05, 0>, grid_offset3 - <0.244, 0, 0>, 0.0, 
       PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.05, 0>, grid_offset4, 0.0, 
       PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.126, 0.05, 0>, grid_offset5 - <0.037, 0, 0>, 0.0 
       ]); 

}

// SALAHZAR intelligent procedure to extract UTF-8 codes and convert to index in our "cp850"-like table integer GetIndex(string char) {

   integer  ret=llSubStringIndex(gCharIndex, char);
   if(ret>=0) return ret;
   
   // special char do nice trick :)
   string escaped=llEscapeURL(char);
   integer found=llListFindList(decode, [escaped]);
   
   // Return blank if not found
   if(found<0) return 0;
   
   // return correct index
   return 100+found;
   

} // END SALAHZAR


RenderString(integer link, string str) {

  // Get the grid positions for each pair of characters. 
  vector GridOffset1 = GetGridOffset( GetIndex(llGetSubString(str, 0, 0)) ); // SALAHZAR intermediate function
  vector GridOffset2 = GetGridOffset( GetIndex(llGetSubString(str, 1, 1)) ); // SALAHZAR
  vector GridOffset3 = GetGridOffset( GetIndex(llGetSubString(str, 2, 2)) ); // SALAHZAR
  vector GridOffset4 = GetGridOffset( GetIndex(llGetSubString(str, 3, 3)) ); // SALAHZAR
  vector GridOffset5 = GetGridOffset( GetIndex(llGetSubString(str, 4, 4)) ); // SALAHZAR
  // 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();
   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;
       
       llSetLinkPrimitiveParams( 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;
       
       llSetLinkPrimitiveParams( 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() {

   llSay(0,"Init beginning...");
   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);
   
   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;

{

       llSay(0,"Scanning bank #"+(string)bank);
   
       //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;
       }
   }
   
   llMessageLinked(LINK_THIS, SLAVE_RESET, "" , null_key);
   //llOwnerSay((string)llGetFreeMemory());
   llSay(0,"Xyzzy initialized.");

}


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);
       }
   } 
   
   touch_start(integer count)
   {
       //llMessageLinked(LINK_THIS,DISPLAY_STRING,"123456789","1");
   }
  
  
   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 == []);
           }
       }
   }

}


</lsl>

Slave Script

<lsl> //////////////////////////////////////////// // XyzzyText v2.1.UTF8 Slave Script by Arnold Wilder // 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"; // 48 pixel font key gFontTexture = "f226766c-c5ac-690e-9018-5a37367ae95a"; // 38 pixel font //key gFontTexture= "ac955f98-74bb-290f-7eb6-dca54e5e4491"; key gFontTexture= "e5efeead-c69e-eb81-e7bd-dad2bb787d2b"; // BitStream Vera Monotype // All displayable characters. Default to ASCII order. string gCharIndex; list decode=[]; // to handle special characters from CP850 page for european countries

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

ResetCharIndex() {

  gCharIndex  = " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"; 
  gCharIndex += "abcdefghijklmnopqrstuvwxyz{|}~"; 
  gCharIndex += "\n\n\n\n\n"; 
    // special UTF-8 chars for European languages // SALAHZAR special chars according to a selection from CP850
    // these 80 chars correspond to the following chars in CP850 codepage: (some are not viewable in editor)
    // rows(11)="Çüéâäàåçêë"
    // rows(12)="èïîìÄÅÉæÆ◄"
    // rows(13)="öòûùÿÖÜ¢£¥"
    // rows(14)="₧ƒáíóúñѪº"
    // rows(15)="¿⌐¬½¼¡«»αß"
    // rows(16)="ΓπΣσµτΦΘΩδ"
    // rows(17)="∞φε∩≡±≥≤⌠⌡"
    // rows(18)="÷≈°∙·√ⁿ²€ "     
    decode= [ "%C3%87", "%C3%BC", "%C3%A9", "%C3%A2", "%C3%A4", "%C3%A0", "%C3%A5", "%C3%A7", "%C3%AA", "%C3%AB" ];
    decode+=[ "%C3%A8", "%C3%AF", "%C3%AE", "%C3%AC", "%C3%84", "%C3%85", "%C3%89", "%C3%A6", "%C3%AE", "xxxxxx" ];
    decode+=[ "%C3%B6", "%C3%B2", "%C3%BB", "%C3%B9", "%C3%BF", "%C3%96", "%C3%9C", "%C2%A2", "%C2%A3", "%C2%A5" ];
    decode+=[ "%E2%82%A7", "%C6%92", "%C3%A1", "%C3%AD", "%C3%B3", "%C3%BA", "%C3%B1", "%C3%91", "%C2%AA", "%C2%BA"];
    decode+=[ "%C2%BF", "%E2%8C%90", "%C2%AC", "%C2%BD", "%C2%BC", "%C2%A1", "%C2%AB", "%C2%BB", "%CE%B1", "%C3%9F" ];
    decode+=[ "%CE%93", "%CF%80", "%CE%A3", "%CF%83", "%C2%B5", "%CF%84", "%CE%A6", "%CE%98", "%CE%A9", "%CE%B4" ];
    decode+=[ "%E2%88%9E", "%CF%86", "%CE%B5", "%E2%88%A9", "%E2%89%A1", "%C2%B1", "%E2%89%A5", "%E2%89%A4", "%E2%8C%A0", "%E2%8C%A1" ];
    decode+=[ "%C3%B7", "%E2%89%88", "%C2%B0", "%E2%88%99", "%C2%B7", "%E2%88%9A", "%E2%81%BF", "%C2%B2", "%E2%82%AC", "" ];} 

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>; //512*512 texture 
  return <-0.45 + 0.1 * Col, 0.425 - 0.05 * Row, 0.0>; //512*1024 texture

}

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> 
   
  llSetLinkPrimitiveParams( link,[ 
       PRIM_TEXTURE, FACE_1, (string)gFontTexture, <0.126, 0.05, 0>, grid_offset1 + <0.037, 0, 0>, 0.0, 
       PRIM_TEXTURE, FACE_2, (string)gFontTexture, <0.05, 0.05, 0>, grid_offset2, 0.0, 
       PRIM_TEXTURE, FACE_3, (string)gFontTexture, <-0.74, 0.05, 0>, grid_offset3 - <0.244, 0, 0>, 0.0, 
       PRIM_TEXTURE, FACE_4, (string)gFontTexture, <0.05, 0.05, 0>, grid_offset4, 0.0, 
       PRIM_TEXTURE, FACE_5, (string)gFontTexture, <0.126, 0.05, 0>, grid_offset5 - <0.037, 0, 0>, 0.0 
       ]); 

}

// Extract UTF-8 codes and convert to index in our "cp850"-like table integer GetIndex(string char) {

   integer  ret=llSubStringIndex(gCharIndex, char);
   if(ret>=0) return ret;
   
   // special char do nice trick :)
   string escaped=llEscapeURL(char);
   integer found=llListFindList(decode, [escaped]);
   
   // Return blank if not found
   if(found<0) return 0;
   
   // return correct index
   return 100+found;

}

RenderString(integer link, string str) {

  // Get the grid positions for each pair of characters. 
  vector GridOffset1 = GetGridOffset( GetIndex(llGetSubString(str, 0, 0)) );
  vector GridOffset2 = GetGridOffset( GetIndex(llGetSubString(str, 1, 1)) );
  vector GridOffset3 = GetGridOffset( GetIndex(llGetSubString(str, 2, 2)) );
  vector GridOffset4 = GetGridOffset( GetIndex(llGetSubString(str, 3, 3)) );
  vector GridOffset5 = GetGridOffset( GetIndex(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; 
  string Token; 
  integer Clipped; 
  integer LastWasEscapeSequence = FALSE; 
  // Work from left to right. 
  for (i = 0; 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; 
              for (j = 1; 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);
       }
   
   } 
   
  

} </lsl>