<!DOCTYPE html>
<html>
<!--
 * Copyright (C) 2013 Neo Visionaries Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
-->
<head>
<meta http-equiv="Content-Type" value="text/html;charset=UTF-8"/>
<meta name="author" content="Takahiko Kawasaki">
<title>JavaScript で文字数、UTF-8 でのバイト数、サロゲートペアの数を数える</title>
<script type="text/javascript">
function compute_bytes_in_utf8(codePoint)
{
  // Unicode のコードポイントと、それを UTF-16BE で符号化した
  // ときの値は等しい。ただし、U+FFFF 以上のコードポイントは除く。

  if (codePoint <= 0x007F)
  {
    // U+0000 - U+007F: UTF-8 で 1 バイト
    return 1;
  }
  else if (codePoint <= 0x07FF)
  {
    // U+0080 - U+07FF: UTF-8 で 2 バイト
    return 2;
  }
  else if (codePoint <= 0xD7FF)
  {
    // U+0800 - U+D7FF: UTF-8 で 3 バイト
    return 3;
  }
  else if (codePoint <= 0xDFFF)
  {
    // 0xD800 - 0xDBFF: 上位サロゲート
    // 0xDC00 - 0xDFFF: 下位サロゲート
    //
    // サロゲートペアで表現される範囲は U+10000 ~ U+10FFFF で、
    // この範囲の文字は UTF-8 で符号化したとき 4 バイトとなる。
    // ちょうどいい具合に、4 は 2 で割り切れる (ここで 2 とは、
    // 上位サロゲート一つと下位サロゲート合わせて二つという意味)。
    // そのため、この実装でここで 2 (= 4 / 2) を返す。
    return 2;
  }
  else if (codePoint <= 0xFFFF)
  {
    // U+E000 - U+FFFF: UTF-8 で 3 バイト
    return 3;
  }
  else
  {
    // U+10000 - ...: UTF-16 ではここには来ない。
    return 0;
  }
}

function count_up()
{
  // 入力と出力に用いる HTML 要素
  var input         = document.getElementById("input").value;
  var outputLetters = document.getElementById("outputLetters");
  var outputBytes   = document.getElementById("outputBytes")
  var outputPairs   = document.getElementById("outputPairs")

  // 文字数、UTF-8 でのバイト数、サロゲートペア数のカウンター
  var nLetters = 0;
  var nBytes   = 0;
  var nPairs   = 0;

  // 入力文字列に含まれる各コードポイントごとに
  for (var i = 0; i < input.length; ++i)
  {
    // その位置にある文字のコードポイントを取得する。
    //
    // charCodeAt() は常に 65,536 より小さい値を返す。より大きい
    // コードポイント (= U+10000 以上) は、サロゲートペアを用いて
    // 表現される。
    var codePoint = input.charCodeAt(i);

    // 下位サロゲートの範囲でなければ
    if (codePoint <= 0xDBFF || 0xE000 <= codePoint)
    {
      // 文字数を数える
      ++nLetters;
    }

    // 上位サロゲートの範囲ならば
    if (0xD800 <= codePoint && codePoint <= 0xDBFF)
    {
      // サロゲートペア数を数える
      ++nPairs;
    }

    // UTF-8 で符号化したときのバイト数を計算する。
    nBytes += compute_bytes_in_utf8(codePoint);
  }

  // 結果を書く
  outputLetters.innerHTML = nLetters;
  outputBytes.innerHTML   = nBytes;
  outputPairs.innerHTML   = nPairs;
}
</script>
<body>
  <!-- 入力 -->
  <input id="input" type="text" onInput="count_up()"><br/><br/>

  <!-- 出力: 文字数 -->
  文字数: <span id="outputLetters">0</span><br/>

  <!-- 出力: UTF-8 でのバイト数 -->
  UTF-8 でのバイト数: <span id="outputBytes">0</span><br/>

  <!-- 出力: サロゲートペアの数 -->
  サロゲートペアの数: <span id="outputPairs">0</span><br/>
</body>
</html>