🫣

TinySegmenterで絵文字が分断されるのを修正する

2023/05/30に公開

TinySegmenterという軽量日本語分割ライブラリがあります。

https://fukuno.jig.jp/3596

上記記事にあるように、このように使用することができます。軽量かつ高速で便利です。

import { TinySegmenter } from "https://code4fukui.github.io/TinySegmenter/TinySegmenter.js";

console.log(TinySegmenter.segment("私の名前はカワリミ人形です"));
// [ "わたし", "の", "名前", "は", "カワリミ", "人形", "です" ]

しかし、絵文字が含まれているときに問題が生じます。が出てしまうのです。

import { TinySegmenter } from "https://code4fukui.github.io/TinySegmenter/TinySegmenter.js";

console.log(TinySegmenter.segment("楽しい🥳"));
// [ "楽しい", "�", "�" ]

console.log(TinySegmenter.segment("楽しい🥳🥳"));
// [ "楽しい", "�", "��", "�" ]

console.log(TinySegmenter.segment("楽しい🥳🥳🥳"));
// [ "楽しい", "�", "��", "�", "🥳" ]

console.log(TinySegmenter.segment("楽しい🥳🥳🥳🥳"));
// [ "楽しい", "�", "��", "�", "🥳", "�", "�" ]

絵文字は、サロゲートペアで表現されています。
前半がU+D800U+DBFF、後半がU+DC00U+DFFFの組で一文字が作られているのですが、これが今回のように分割されると、が表示されてしまいます[1]

https://ja.wikipedia.org/wiki/Unicode#サロゲートペア

ということで、以下のようにして修正できそうです。

  • とりあえずTinySegmenterで処理を行う
  • 結果を見て、分断されたサロゲートペアがあれば接続する

実装例を示します。

import { TinySegmenter } from "https://code4fukui.github.io/TinySegmenter/TinySegmenter.js";
const mySegmenter = (text: string) => {
  const segments = TinySegmenter.segment(text);
  const result = [segments[0]];

  // join separated surrogate pairs
  for (const seg of segments.slice(1)) {
    const last = result.at(-1) || "";

    if (/[\ud800-\udbff]$/.test(last) && /^[\udc00-\udfff]/.test(seg)) {
      result.splice(-1, 1, last + seg);
    } else {
      result.push(seg);
    }
  }

  return result;
};

console.log(mySegmenter("楽しい🥳"));
// [ "楽しい", "🥳" ]
console.log(mySegmenter("楽しい🥳🥳"));
// [ "楽しい", "🥳🥳" ]
console.log(mySegmenter("楽しい🥳🥳🥳"));
// [ "楽しい", "🥳🥳", "🥳" ]
console.log(mySegmenter("楽しい🥳🥳🥳🥳"));
// [ "楽しい", "🥳🥳", "🥳", "🥳" ]

TinySegmenterによって作られた項目をループして配列を作り直しています。
この際、「直前の項目の終端がサロゲートペアの前半」かつ「現在の項目の開始がサロゲートペアの後半」であれば分断されたペアなので、それらを接続したものを配列に入れ直します。

脚注
  1. このダイヤの中にハテナが入った文字は置換文字(replacement character, U+FFFD)といい、正しく表示できない文字は全てこれに置き換わることになっています ↩︎

Discussion