This issue is with this line. The JString::strlen above is UTF-8 safe, but ord and the {} index aren't - so the string lengths with and without UTF-8 are different.

This line should probably read:

$ch = ordutf8(JString::substr($str, i, 1));

where ordutf8 is a hack of the function defined at http://www.php.net/manual/en/function.ord.php#109812 :

function ordutf8($string) {
    $code = ord(substr($string, 0,1)); 
    if ($code >= 128) {        //otherwise 0xxxxxxx
        if ($code < 224) $bytesnumber = 2;                //110xxxxx
        else if ($code < 240) $bytesnumber = 3;        //1110xxxx
        else if ($code < 248) $bytesnumber = 4;    //11110xxx
        $codetemp = $code - 192 - ($bytesnumber > 2 ? 32 : 0) - ($bytesnumber > 3 ? 16 : 0);
        for ($i = 1; $i < $bytesnumber; $i++) {
            $code2 = ord(substr($string, $i, 1)) - 128;        //10xxxxxx
            $codetemp = $codetemp*64 + $code2;
        }
        $code = $codetemp;
    }
    return $code;
}