Ура! Проблему решил!
Причина была вот в чем: многие иконки emoji состоят из суррогатных пар - например, \ud83d\ude0d. Это 2 символа. Но регулярка почему-то засчитывает эту комбинацию как один символ. В итоге все найденные индексы сдвинуты. Но если воспользоваться методом length у класса String, то длина текста выводится верно.
Код AS3:
trace(String("\ud83d\ude0d").length); // выведет 2
Отсюда пришел к выводу, что надо попробовать искать индексы хэштегов не через RegExp, а через методы String.
В итоге все хэштеги нахожу через RegExp и запоминаю в массив, а потом прохожусь по этому массиву и ищу эти найденные хэштеги в тексте с помощью indexOf, записывая индексы начала и конца хэштега.
Код AS3:
var txt:TextField = new TextField();
txt.x = txt.y = 15;
txt.autoSize = TextFieldAutoSize.LEFT;
addChild(txt);
txt.text = "#Dog \ud83d\ude0d #Test \ud83d\ude0e #Night Всякий текст с \n#хэштегами и смайликами";
var arr:Array = searchHashtag(txt.text);
var baseFormat:TextFormat = new TextFormat(new PartnerCondensedBold().fontName, 30, 0xFFFFFF);
txt.setTextFormat(baseFormat);
var tagColorize:TextFormat = new TextFormat(null, null, 0x66FE03);
for (var i:uint in arr)
txt.setTextFormat(tagColorize, arr[i][0], arr[i][1]);
function searchHashtag(str:String):Array
{
var arr:Array = new Array();
var pattern:RegExp = /#[\S]+/g;
arr = str.match(pattern);
var res:Array = new Array();
var index:uint = 0;
var s1:uint;
var s2:uint;
for (var i:uint in arr)
{
s1 = str.indexOf(arr[i], index);
s2 = s1 + arr[i].length;
res.push([s1,s2]);
index = s2;
}
return res;
}
И кстати, emoji тоже получилось отображать прямо в тексте - просто создал текстовое поле программно и они отобразились