本文へ移動
Develop Tools
← 使い方ガイドへ戻る

おしゃれ文字を使うと文字数が増える?SNS・入力制限で長さが合わない原因

見た目では1文字の𝐀も、JavaScriptのString.lengthでは2になります。サービスがどの単位で上限を数えるかによって、同じ表示でも残り文字数が変わります。

𝐀を1 Grapheme・1 Code Point・2 UTF-16コード単位・4 UTF-8バイトで示す図
𝐀を1 Grapheme・1 Code Point・2 UTF-16コード単位・4 UTF-8バイトで示す図

Grapheme・Code Point・UTF-16・UTF-8を数える

文字数カウントツールで見た目の文字数、Unicode Code Point、UTF-16コード単位、UTF-8バイトを同時に確認できます。

結論:変換後の文字列を、利用先と同じ単位で数え直す

Mathematical Alphanumeric Symbolsの多くは補助平面にあり、1 Code PointをUTF-16のSurrogate Pair 2単位で表します。結合装飾はCode Point自体が増えます。

SNSやApplicationがGrapheme、Code Point、UTF-16、UTF-8 Byteのどれを採用するかはサービスごとに異なります。推測せず実際の入力欄でも確認します。

「1文字」の意味を仕様へ明記しないと、FrontendとBackendで上限判定がずれます。

制限単位を特定する

同じ文字列を4単位で数え、どの値が利用先のカウンターと一致するか確認します。

DBでは文字数上限とは別にUTF-8 Byte、Index長、Encodingの4-byte対応も確認します。

確認項目確認方法判断
GraphemeIntl.Segmenter見た目の文字数
Code PointArray.from/for...ofUnicode文字数
UTF-16String.lengthJavaScript長
UTF-8TextEncoder保存・通信Byte

文字数上限へ合わせる手順

  1. 通常文字と変換後の文字列を保存します。
  2. 文字数カウントツールへ変換後Textを貼ります。
  3. Grapheme・Code Point・UTF-16・UTF-8を記録します。
  4. 利用先の実カウンターと照合します。
  5. 上限を超える場合は装飾範囲または文字数を減らします。

変換前のASCII文字数をそのまま採用しません。

スタイルで増え方が違う

入力/結果GraphemeCode PointUTF-16
A111
𝐀112
122
👩‍💻135

CSSのフォント変更ではなくUnicode文字への置換

方法文字列内部で変わるもの
CSS FontA → AU+0041のままGlyphをFontで描き分ける
Fancy UnicodeA → 𝐀U+0041からU+1D400へCode Point自体を置換する
結合装飾A → A̲U+0041の後ろへCombining Low Lineを追加する

Mathematical Alphanumeric Symbolsは数式中の意味を区別するために定義された文字群です。SNS向けのおしゃれ文字は、それらを装飾目的で利用するケースが多く、CSSのfont-familyをコピーしているわけではありません。

NFC・NFD・NFKC・NFKDは目的を分けて使う

形式主な処理装飾文字への影響
NFCCanonical Decomposition後に可能な文字をComposition正準等価な表現をそろえるが、すべての装飾をASCIIへ戻すものではない
NFDCanonical DecompositionéをeとCombining Acute Accentへ分解する例がある
NFKCCompatibility Decomposition後にComposition𝐀、𝔸、A、①等がAや1へ変わる場合がある
NFKDCompatibility Decomposition互換文字を分解し、さらに結合列になる場合がある

NFKCは一部のおしゃれ文字を普通の英数字へ近づける手段ですが、上下反転、Small Capsの近似文字、結合下線などを完全に元へ戻す汎用逆変換ではありません。元表記を保存する必要があるデータへ自動適用しません。

文字数はGrapheme・Code Point・UTF-16で分ける

数え方𝐀の値用途
Grapheme Cluster1利用者が見た目で認識する文字数
Unicode Code Point1(U+1D400)文字の割り当て番号とUnicode処理
JavaScript String.length2 UTF-16 Code UnitsJavaScriptの添字・既存の入力制限
UTF-84 BytesDB、API、File、通信時の保存容量

結合文字やZWJ Emojiは複数Code Pointでも1 Graphemeに見える場合があります。JavaScriptで見た目の文字数を数えるならIntl.Segmenter、Code Point単位ならfor...ofやArray.fromを使い、split("")だけに依存しません。

DevelopToolsで確認できる範囲と、できない範囲

項目現行ツールの動作
入力英字・数字を中心に変換し、日本語、絵文字、改行、未対応記号は削除せずそのまま残す
21スタイルSerif/Sansの太字・斜体、Monospace、Script、Fraktur、Double-Struck、Circled、Squared、Small Caps、上付き・下付き、上下反転、取り消し線・下線
Unicode処理Intl.Segmenterが使える場合はGrapheme Cluster単位で処理し、使えない場合もArray.fromでSurrogate Pairを分割しない
表示確認スタイルごとの変換文字数と対象文字数を表示し、未対応文字を見つけやすくする
保存各結果のコピー、TXT保存、お気に入り、最近コピーしたスタイルの並べ替えに対応
未対応全角・括弧囲み、汎用的な逆変換、NFC/NFD/NFKC/NFKD実行、Unicode名・Code Point解析、Mixed Script/Confusable検出
データ保持入力文と変換結果はServerへ送信せず、LocalStorageへも保存しない。表示設定とお気に入りだけを端末内へ保存

文字を普通の英数字へ戻す、正規化結果を比較する、Code Pointを解析する場合はUnicodeコード表を併用します。文字数・UTF-8バイト・UTF-16コード単位を比べる場合は文字数カウントツールを使います。

表示互換性・検索性・アクセシビリティを確認する

  • 利用先のFontにGlyphがなければ□や空白に見えるため、実際の端末とApplicationへ貼り付けて確認する
  • プロフィール本文で表示できても、UsernameやIDの許可文字・文字数・正規化規則を満たすとは限らない
  • 検索、読み上げ、音声入力、Copy後の再編集が必要な重要情報には通常文字を併記する
  • Latin・Greek・Cyrillic等の見た目が似た文字を識別子へ混在させず、なりすましや誤認へ利用しない
  • Password、URL、Email Address、Source Code、署名対象データへ装飾Unicodeを使わない

DevelopToolsで表示できることは、すべてのOS、SNS、Browser、Applicationで同じGlyphが表示される保証ではありません。Platformの仕様は変わるため、特定サービスで必ず使えるとは案内しません。

UnicodeとJavaScriptの一次資料

NormalizationはUAX #15、Grapheme ClusterはUAX #29、ConfusableとIdentifier SecurityはUTS #39を基準にします。JavaScriptの実装はMDNのString.length、normalize()、Intl.Segmenterの説明と照合します。

具体例:10文字のASCIIを数学太字へ変換する

見た目は10文字でもUTF-16では英字1文字あたり2単位になる場合があります。

Grapheme=10、Code Point=10、UTF-16=20という結果になり得ます。

  1. 変換前を計測
  2. 太字へ変換
  3. 変換後を再計測
  4. 入力上限と照合

BackendがUTF-8 Byte上限ならUTF-16値だけでは判定できません。

よくある質問

変換した文字はすべての端末で同じように表示されますか?
保証できません。Code Pointが正しくても、OSやApplicationのFontがGlyphを持たなければ□になり、Fontの違いで字形も変わります。利用先で実表示を確認してください。
入力した文章はServerへ送信または保存されますか?
現行の特殊文字・おしゃれ文字変換ツールはBrowser内で変換し、入力文と結果をServerやLocalStorageへ保存しません。端末内へ保存するのは表示設定とお気に入りだけです。
NFKCを実行すれば必ず普通の文字へ戻せますか?
戻せるのは互換分解が定義された文字です。上下反転、近似文字、独自Mapping、結合装飾等はLosslessに戻せない場合があるため、元文字列を保持してください。