Chapter 05

コマンドプロンプトとサロゲートペア縛り

zetamatta
zetamatta
2021.02.07に更新

そんなわけで、ここまでは「Windows はやろうと思えば、普通に UTF8 をプログラムで使えるよ」という話でした。ここからは「実は使えないコードもあるんだ(あったんだ)」という話です。

そう、コマンドプロンプトではたとえフォントが用意されていても表示できない Unicode があるんです。U+10000~ 以降の Unicode は表示できません。

> えらくキリのいいところがら表示できないんだな <

はい。UTF16の1コードで収まりきれず、サロペートペアで表現しなければいけない文字がアウトなんです。要は桁が用意されていなかったんですよ。WindowsのAPIには、コンソールに今表示されている文字情報の読み書きを行うReadConsoleOutput 関数WriteConsoleOutput関数というものが用意されています。言ってみればテキストVRAMのAPIみたいなもんですが、ここで使われている画面の1セルの情報を格納する CHAR_INFO構造体の構造を見ると

typedef struct _CHAR_INFO {
  union {
    WCHAR UnicodeChar;
    CHAR  AsciiChar;
  } Char;
  WORD  Attributes;
} CHAR_INFO, *PCHAR_INFO;

> 1文字につき文字コードが16bit分(WCHAR)しか用意されてへん…(絶句)<

これはどうしようもありません。セル位置というのは「画面の桁数×現在の行位置+現在の桁位置」で場所が一意に決まりますから、あふれた分をとなりに書くというわけにはいきません。さすがにこれでサロゲートペアな文字をコンソールで表示するのは Windows では無理筋だなぁ…とガックシせざるを得ませんでした。

だが、しかし、実はとんでもない解決法があったのです。

「その API だけサポートしなければいいじゃない」

https://docs.microsoft.com/ja-jp/windows/console/readconsoleoutput

このドキュメントでは、 エコシステムのロードマップ の一部ではなくなったコンソールプラットフォームの機能について説明します。 このコンテンツは新しい製品では使用しないことをお勧めしますが、今後も引き続き既存の使用をサポートします。 この最新のソリューションでは、クロスプラットフォームのシナリオで最大の互換性を実現するための 仮想端末シーケンス に焦点を当てています。 この設計の決定に関する詳細については、 従来のコンソールと仮想端末 のドキュメントを参照してください

マジか…やってくれたな、Microsoft

そう、画面の入出力する関数は他にもあって、そっちは別にセルのビット数縛りはないんです。というか、ReadConsoleOutput や WriteConsoleOutput もそんなに多用される API でもないので「思い切って切り捨てる」という戦略が有効だったのです。
(それに UNIX系のシステムの端末APIなんかは、コンソールバッファなんてそもそも持ってないんだから、そう無茶な話でもない)

そういった思い切った仕様の解釈で WindowsTerminal では美しい絵文字の表示が可能になったというわけです。