XyzzyText-UTF8

Scripting tools to allow display of text on a prim: XyText 1.5 (aka XyText) , XyzzyText, XyyyyzText, XyText-UTF8, XyzzyText-UTF8, ZZText, VariText
XyzzyText doesn't work with European characters.. :( See also ZZText for a comprehensive International low-prim solution. XyText-UTF8 for the a full programmatic solution with standard xytext. I eventually found out that XyzzyText is really slower than XyText so I'm showing a comprehensive solution with xytext in previous link.
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 XyzzyText 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>