みずぴー日記

人間の再起動ボタンはハワイのビーチにある

🐟日本語入力の落とし穴 #burikaigi

BuriKaigi2018で発表した。 入力メソッドについてのテキストを書いてる途中なので、それを流用する目論見だったが、まったく別のものになった。

🎤原稿

あいさつ

f:id:mzp:20180204083441j:plain

このセッションはJava+αだと聞いてるので、+α部分の寛容さに賭けて日本語入力の話をする。

日本語を受け取るアプリケーションがおちいりがちな落とし穴についての話をするので、テキスト入力を受けつけるアプリケーションを作るときとかに役に立つと嬉しく思う。

会社の紹介

f:id:mzp:20180204083444j:plain

ボクはMisocaという請求書を管理するWebサービスを作っている会社に勤めている。

日本語入力の重要性

f:id:mzp:20180204083447j:plain

Misocaの開発には、様々なサービス・ソフトウェアを使っている。RubyRailsやSlack、Githubなどは使っている会社も多いと思う。

f:id:mzp:20180204083451j:plain

この図には記載されていないが日本語入力の使用頻度も極めて高い。

アンケート

f:id:mzp:20180204083455j:plain

さて、ここでちょっとアンケートを取りたいと思う。使っている日本語入力を教えてほしい。

(補足)1〜3は同数くらい、その他は2人くらいSKKユーザーが居た。

SKK

f:id:mzp:20180204083501j:plain

ボクは「その他」に属するSKKという日本語入力を使っている。

ニコニコ大百科の記事が詳しいので引用します。「この慣れると他のが使えなくなる」「様々なプラットフォームに移植されている」が特徴的である。

f:id:mzp:20180204083505j:plain

移植作業はボクも行なっており、Mac版のAquaSKKは180+スター、iOS版のFlickSKKは3000個くらいインストールされてるので、需要はそれなりにある。

日本語入力の特殊性

f:id:mzp:20180204083616j:plain

作っていてつくづく感じるが日本語入力は特殊なソフトウェアである。

  • すべてのキー入力を受け取る。 パスワード入力欄を含めてすべてのキー入力を取得できる。
  • メインウインドウのような固有のウインドウは持たないがアプリケーションと強調して画面表示を行なう。デーモンに近いソフトウェアである。
  • 日本では圧倒的なシェアを誇る。

日本語以外の入力方法

では日本語以外の入力方法を紹介する。 このために、5ヶ国語くらいで「こんにちは」と書けるようになった。

キーボードによる入力

f:id:mzp:20180204083621j:plain

英語は専用のキーボードがあるので、入力メソッドが必要ない。 その他にも( ゚д゚)の口でおなじみのキリル文字が入力できるキーボードもある。

韓国語入力メソッド

f:id:mzp:20180204083626j:plain

韓国語やヒンディー語でも入力メソッドが用いられるが、これも日本語入力とはちょっと違う。

例えば、韓国語の表記に使われるハングルは、複数の字母の組み合わせで文字を構成する。例えば아というハングルは、ㅇとㅏという2つの記号の組み合わせで構成する。

それぞれが字母がキーと対応している。ㅇはDキーとㅏはKキーに対応しているので、DKと打つことで아が入力できる。

注目すべきは日本語入力における「変換候補の表示」に相当する動作がない点である。文字の数が漢字に比べて圧倒的に少ないのが原因だと推察している。

中国語入力メソッド

f:id:mzp:20180204083629j:plain

中国語入力は日本語入力と類似点が多い。

中国語の入力にもいくつかの種類があるが、ピンイン入力を取り上げる。この入力方式では、ni-haoといれたのち、スペースをおして漢字を選択することで入力する。

顔文字がでてくるのがかわいい。

発音記号の入力、候補の選択という流れは日本語入力と類似している。

漢字入力の特殊性

f:id:mzp:20180204083634j:plain

なので

  • 発音記号を入力する
  • 候補を表示して、そこから入力する文字も選択する

という二段階の変換は漢字を持つ日本語・中国語のための仕組みであると言える。歴史的には韓国語・ベトナム語も漢字を持つが、現代では日常的には使われていないため入力メソッドではサポートされていない。

(補足)懇親会でトンパ文字の話はしたが、こちらは入力メソッドをもたないため今回の対象外である。

つらい話

f:id:mzp:20180204083638j:plain

このように日本語入力は特殊な存在であるため、日本語入力と相性がよくないアプリケーションが存在する。 そのようなアプリケーションを作ってしまわないために、つらかった話を紹介する。

日本語入力の仕組み

f:id:mzp:20180204083642j:plain

最初に日本語入力の基本的な仕組みについて説明する。これはmacOSの例だが、おそらく他のプラットフォームでもそれほど大きな違いはないはずである。

日本語入力は、キーボード入力とアプリケーションの間に入りテキストを変換する。

そして日本語入力は「漢字変換エンジン」と「入力メソッド用API」の2つで構成されている。 「漢字変換エンジン」は入力されたひながななどを漢字に変換するエンジンである。これは、「日本語入力を支える技術」で詳しく解説さている。

「入力メソッド用API」は漢字変換エンジンとアプリケーションをつなぐためのAPIである。 これを使うことで、アプリケーションに渡されたキー入力をうけとり、なんらかの形で漢字に変換した上で、アプリケーションに返すことができる。

平和な世界の崩壊

f:id:mzp:20180204153850j:plain

この仕組みにより、キーを押すとその文字が入力される素朴で穏かな世界が失なわれる。

具体的には

  • 「確定する前の文字列」という概念が導入される。
  • 「入力メソッド用のキー」という概念が導入される。

といった変化がある

未確定文字列の導入による問題

f:id:mzp:20180204083655j:plain

入力メソッドにより「確定する前の文字列」という概念が導入される。 これは、下線つきで表示されることが多い。 入力されてるとも入力されてないとも言えない微妙な存在である。

Ulysses IIIの実例

f:id:mzp:20180204083700j:plain

Ulysses IIIで起きた問題について説明する。

f:id:mzp:20180204083706j:plain

Ulysses IIIにはMarkdownの文法をハイライトする機能がある。 例えば ** で囲まれてる部分がボールドになる。

しかし、この機能は未確定文字列に対しても有効なので、未確定文字に * が含まれていると、それ以降の文字がボールドに変換される。 さらにその副作用で、未確定文字が確定されてしまう。

余談ですが、これはバグレポメールを送ったら「…よくわかんないんだが、押したキーに対応した文字が入力されないんだったら、君たちはどうやってアルファベットを入力するんだ?」という返事が返ってきて大変だった。

VisualStudioCodeの実例

f:id:mzp:20180204083709j:plain

次はVisualStudioCodeの実例を紹介する。 すでに修正ずみだが、初期のバージョンは未確定文字列のハンドリングがうまくいっておらず、未確定文字列が消失していた。つまり事実上、日本語の入力ができなかった。

f:id:mzp:20180204083715j:plain

バグレポートを送るために、メニューから "report issue" を選択したら、メーラーが起動してびっくりした。 さらに、メールを送ったら、エラーメールが返ってきてさらにびっくりした。 「How to fix」のとこに受信するグループの管理者にインスタンメッセンジャーか電話をして許可を貰えとか書いてあって無茶すぎると思った。

宛先がmonacotoolsになってて、「なぜモナコ????」と不思議だったが、これはVisualStudioCodeのエンジンの開発コードらしい。

ちなみに2~3週間後に「バグレポありがとう。韓国語の入力メソッドでも同様の問題がレポートされてたので、修正したよ」って返ってきた。よかった。

PowerPointの実例

f:id:mzp:20180204083720j:plain

続いてパワーポイントの実例について紹介する。

f:id:mzp:20180204083724j:plain

日本語は未確定文字列を経由して入力されることが多い。

しかし、入力メソッドの仕様上、この流れは必須ではなく、直接確定文字列を挿入しても問題ない。例えばaを押すと確定文字としての「あ」が挿入される入力メソッドは仕様に対して妥当である。 事実、SKKはそれを行なっている。

しかしパワーポイントは未確定文字列を経由せずにテキストを挿入すると取りこぼしが発生する。 たぶん、未確定文字列をいれたあと確定するという流れを仮定した処理が入っているのだと思う。

パワーポイントでは必ず未確定文字列をいれたのちに確定文字列をいれることで回避している。

「入力メソッド用キー」による問題

f:id:mzp:20180204083727j:plain

「A」キーが打たれたら「あ」になる、「半角・全角」キーが押されたらIMEの状態がかわる、「Ctrl-S」はIMEが処理しないので自前で処理する、といったように入力メソッドと協調してキー入力の扱いを決める必要がある。

iTerm2の実例

f:id:mzp:20180204083731j:plain

iTerm2という端末エミュレータにおける例を紹介する。

f:id:mzp:20180204083734j:plain

これのキー入力時の処理は以下のようになっている。

  1. キー入力のイベントハンドラが起動する
  2. 入力メソッドにキー入力イベントを渡す
  3. 入力メソッドがテキスト入力をした場合は、なにもしない
  4. 入力メソッドがテキスト入力をしなかった場合は、独自に処理をする

一見問題なさそうだが、実は4に問題がある。 テキスト入力がないと、入力メソッドが処理していないは等価ではない。

f:id:mzp:20180204083739j:plain

たとえば「かな」キーを押したときの処理を考えてみる。

まずは入力メソッドが受け取り、入力モードが変更される。テキスト入力が発生しないため、その後iTerm2が処理する。しかしかなキーには機能が割り当てられていないため、なにも起きない。

次にかなキーではなくLキーで、入力モードが切りかわる入力メソッドを考えてみる。SKKがそれに該当する。

同じく入力メソッドが最初に受け取り、入力モードが変更される。テキスト入力が発生しないためiTerm2は処理する。そしてLが入力される。

1度しか入力していないのに、入力メソッドとiTerm2の二重でキー処理されてしまう。 これはSKK界隈で「Lを押したらLが入力された」問題として知られている。

f:id:mzp:20180204083743j:plain

正しく処理するにはテキスト入力が発生したかどうかではなく、入力メソッドがイベントを処理したかどうかで分岐する必要がある。

f:id:mzp:20180204083748j:plain

余談ですが、このパッチの説明は大変だった。

キー入力のイベントハンドラが激戦区で

  • Ctrl-Cのような割り込みキーの処理をしたい
  • ドイツ語のようなアクセント記号つきの文字をいれたい
  • 日中韓の入力メソッドに対応したい

のように、さまざまな要求に対応するために変更が繰替えされていた。blameするとすごいことになっている。

また、入力メソッドの処理のドキュメントがあまりなくて、なぜこの処理が正しいのかを説明するが難しかった。文書の解釈についてディスカッシャンすることが多かった。

今は、パッチがマージされたので「Lを押したらLが入力されてしまう」問題は解決している。

InteliJ IDEAの実例

f:id:mzp:20180204083752j:plain

JetBrains社のIDEにも同様の問題がある。 コードは公開されてないので原因はわからないですが、おそらく原因も同様だと思う。

f:id:mzp:20180204083756j:plain

レポートはあげてあり、人もAssignされているが、ここ2年ほど動きがない。

f:id:mzp:20180204083805j:plain

ので、回避策が必要である。

先程の処理の流れをみてみると、4に行くと問題のある動きが生じる。 なので、常にテキストが入力されるようにすれば、この問題を回避できる。

そのためにNUL文字(\0)を使う。 これは通常のテキストエリアにいれる文字としては不正なのでIDEが無視する。 しかしテキストは入力されていると認識されるので4に行くことはない。

これ、AquaSKKではかなり前から利用されている回避策である。

Qt5の実例

f:id:mzp:20180204083809j:plain

Qtも同様の問題がある。

作るときの苦労

f:id:mzp:20180204083814j:plain

最後に、作るときの苦労の話も少し紹介する。

資料不足

f:id:mzp:20180204083822j:plain

macOSの入力メソッド作るためのライブラリはInputMethodKitという名前で用意されている。しかし、InputMethodKitのドキュメントはほとんどない。

例えば、Qiitaで検索しても0件である。

存在する資料

f:id:mzp:20180204083842j:plain

Appleのドキュメントも特に説明が記載されていない。

参考になるのはヘッダファイルにコメントや、公式サイトから消えたミラーのWebArchiveが文書などである。

孤独

f:id:mzp:20180204083846j:plain

ドキュメントが少ないのと関連するが、弄ってる人はほとんどいない。

動かないメソッド

f:id:mzp:20180204083912j:plain

一部のメソッドがうまく動作しない。

これは変換候補を選択するためのメソッドをディスアセンブルしたコードだが、スタックにpushしてpopしているだけで何もしていない。

radarにレポートしたら「既知のバグ(duplicated)」と帰ってきた。直してほしい。

まとめ

f:id:mzp:20180204083922j:plain

最後にまとめる。

  • 日本語入力は特殊なソフトウェアなので落とし穴がある -「未確定文字列」「入力メソッド用のキー」が特につらい
  • みんな入力メソッドで遊ぼう