В AS3 в классе TextField добавились новые методы типа getCharBoundaries, что значительно упрощает подстановку смайликов на свои места.
Технология прежняя, в тексте заменяем смайлик на пробел, а поверх текста в нужное место аттачим мувик со смайлом.
Ниже код основного класса, когда я проводил эксперименты с этим. Что он делает:
1. Получает текст одной мессаги в которой смайлы заменены на спецсимвол за которым идет номер смайла, типа: "test :bigsmile: end" --> "test @01 end"
2. Составляется таблица стилей под каждый смайлик
setStyle(".smile"+i, {letterSpacing:smile_mc.width});
т.о. один символ такого стиля будет шириной в мувик смайла.
3. Выводим в текстовое поле текст по частям до каждого смайлика
3.1. htmlText+="test "
3.2. вместо смайла ставим пробел со стилем смайла. htmlText+='<SPAN class="smile'+smileNum+'"> </SPAN>';
3.3. вычисляем прямоугольник этого пробела. getCharBoundaries( tf.text.length-1 );
3.4. аттачим мувик смайла точно поверх пробела.
3.5. повторить... htmlText+=" end"
Собственно все.
По результатам тестов, лучше выводить не весь текст чата в одно текстовое поле (чат в 20кб. парсился более 3х секунд), а каждую мессагу в отдельный мувик с текстом и смайлами (теже 20кб. парсились 300мс.)

Код:
/**
* Одна строка чата
* @author Merlin
* @version 1.0
*/
package com.chat {
import flash.display.Sprite;
import flash.display.DisplayObject;
import flash.utils.getDefinitionByName;
import flash.text.TextField;
import flash.text.StyleSheet;
import flash.geom.Rectangle;
import com.controls.Skin;
import com.chat.ChatConstants;
public class ChatOneLine extends Sprite {
/** html-текст */
public var text:String;
/** ник отправителя */
public var from:String;
/** ник отправителя в нижнем регистре*/
public var from_lc:String;
/** сообщение для меня или от меня (нужно подсветить) */
public var me:Boolean;
/** кому адресовано "[ник1][ник2]..." */
public var to:String;
/**
* Одна строка в чате
* @param text html-текст
* @param from от кого
* @param to к кому
* @param me подсветить
*/
function ChatOneLine(text:String, from:String="", to:String="", me:Boolean=false) {
this.text=text;
this.from=from;
if (from) this.from_lc=from.toLowerCase();
this.to=to;
this.me=me;
}
/** Удалить все нарисованное */
public function clear():void {
while (numChildren>0) removeChildAt(0);
}
/**
* Рисует строку чата
* @param width ширина окна чата
*/
public function draw(width:Number):void {
if (numChildren>0) { // текст уже был отображен.
if (oldWidth == width) return; // ширина такая же, ничего не трогаем
// ширина изменилась, удаляем старое и выводим все заново
clear();
}
// аттачим скин
var bg:DisplayObject = addChild( Skin.getSkinElement(me?"skin_chat_message_selected":"skin_chat_message") );
var i:Number, j:Number, c:Number, txt2:String;
var smileCnt:Number = 0; // счетчик смайлов
var s:String = text;
var smile_mc:DisplayObject;
var r:Rectangle;
// Создаем стиль текста
if (!textStyle) {
textStyle = new StyleSheet();
textStyle.setStyle("body", ChatConstants.stylesText);
textStyle.setStyle(".dt", ChatConstants.stylesDate);
textStyle.setStyle(".to", ChatConstants.stylesTo);
textStyle.setStyle(".private", ChatConstants.stylesPrivate);
textStyle.setStyle("a", ChatConstants.stylesHref);
textStyle.setStyle("a:hover", ChatConstants.stylesHrefHover);
// создадим стили под все смайлы
for (i=0; i<=ChatConstants.smileMaxNum; i++) {
smile_mc = Skin.getSkinElement(ChatConstants.smilePrefix+i);
textStyle.setStyle(".smile"+i, {letterSpacing:smile_mc.width});
}
}
// выводим текст
var tf:TextField = bg.label;
i = width - this.width;
tf.width += i;
bg.bg.width += i;
tf.height = 200;
tf.styleSheet = textStyle;
tf.htmlText = "<body>";
// Теперь добавляем текст по частям, до смайла
while ((i=s.indexOf(ChatConstants.smileSymbol))>=0) { // нашли очередной смайл
// вычислим что за смайлик
var smileNum:Number = Math.max(0, Math.min(ChatConstants.smileMaxNum, parseInt(s.substr(i+1,2), 10)));
txt2 = s.substr(i+3);
if (smileCnt < ChatConstants.smileMaxCount) {
// Приаттачим
smile_mc = addChild( Skin.getSkinElement(ChatConstants.smilePrefix+smileNum) );
// ставим вместо смайла пробел шириной с мувик смайла
tf.htmlText += s.substring(0, i)+'<SPAN class="smile'+smileNum+'"> </SPAN>';
// смещаем мувик смайла
r = tf.getCharBoundaries( tf.text.length-1 );
smile_mc.y = r.top + r.height/2;
smile_mc.x = 2 + r.left;
} else { // уже максимально смайлов
tf.htmlText += s.substring(0, i)+' ';
}
s = txt2;
smileCnt++;
}
// и добавляем оставшийся текст
tf.htmlText += s;
// ограничим размер мувика по высоте текста
tf.height = (bg.bg.height = tf.textHeight+4) + ChatConstants.spaceBetweenMessages;
oldWidth = width;
}
/**
* -----------------------------------------------
* private
* -----------------------------------------------
*/
// при изменении стилей в ChatConstants нужно будет обнулить эту переменную
// чтобы стили пересоздались
private static var textStyle:StyleSheet;
private var oldWidth:Number;
}
}