Chapter 09

異体字 - 触れ得ざる者(WindowsTerminal)

hymkor
hymkor
2021.02.10に更新

日本語を始めとして世界中の文字では「ほぼ同じ字だが、字の一部がほんのちょっとだけ違うバリーエーション」が結構あります。それぞれ別のコードに割り当てられることもありますが、それをやっちゃうとキリがないので一つのコードポイントにまとめられてしまっている場合もあります。でも、それだと(おそらく戸籍とかの表記で)困るため、接尾に区別するためのコードを追加することになってます(そういうことをやるから実装が複雑になるんだよ)。その区別するためのコード、それが異体字セレクタというものです。詳しくはこちらを:

邊(U+908A)、邊󠄄(U+908A U+E0104:本当はシンニョウ部分の点が一つ)を例に検証してみました。

Windows で検証したところ:

  • 入力
    • Web などで {U+908A U+E0104} をコピーした時
      • クリップボードには {U+908A U+E0104}とセレクタのコードも含めて記憶される
      • コンソールの右クリックによるペーストでは U+E0104 はパスされて U+908A だけになってしまう
  • 出力
    • コマンドプロンプトでは、セレクタコードは <?> マークになってしまう
    • WindowsTerminal では、セレクタコードのついた文字はフォントは一応変わり(部首の一部が微妙に変わってるだけなのでわかりにくい){U+908A U+E0104} で3セル分になるように表示が少し変わる
package main

import (
	"fmt"
)

func main(){
	fmt.Printf("[%c][%c%c]\n",0x908A,0x908A,0xE0104)
}

普通に表示できるんだから、一行入力の入力も楽勝でしょう。合字も出来たんだから同じように出来るやろ?そう思っていた時期が私にもありました。

ふぁっ?(変な声出た)。何かおかしい。どうも、カーソル位置の認識がずれているようです。

package main

import (
	"fmt"
)

func main() {
	fmt.Printf("[%c][%c%c]\n", 0x908A, 0x908A, 0xE0104)
	fmt.Printf("[%c][%c%c", 0x908A, 0x908A, 0xE0104)
	fmt.Printf("]\n")
}

どうも、異体字セレクタを出力した後のカーソル位置の認識がおかしくなっているようです。出力を止める前は3文字と認識しているのに、止めた後に4文字に変わってしまってます。これは困ります。というのも、一行入力での文字列出力は Windows の API にテキストを流すまで、io.Writer・bufio.Writer・go-colorable など多数の層を挟んでいるため、都合よくここで出力を止めるなどということはできないのです。うーむ。

半日ほど悩んだ結果、異体字は

fmt.Fprintf(w, "    \x1B7\b\b\b\b%s\x1B8", string(s))

と出力することで、回避しました。

  1. 4文字分位置まで、ますスペース文字で移動
  2. カーソル位置を ESC 7 で記憶
  3. 元の位置までバックスペースで復帰
  4. 異体字を出力する
  5. ESC 8 で 2. で記憶した4文字分の位置まで移動

つまり、常に4文字分のサイズであるかのようにカーソル位置を調整するようにしたのです。やったね!

なお、この試行錯誤の過程については zenn のスクラップ「Unicodeとの異体字バトルがはじまったぜ」で記されているので、よろしければご覧ください。