Handling Unicode #2

だいたい書き終わった。これはとても有用なクラスなので wasavi とは独立したリポジトリとして github にあげた。

Unistring
https://github.com/akahuku/unistring

Handling Unicode

wasavi が正しくサロゲートペアや書記素クラスタと言った、Unicode のめんどくさいトピックを正しく処理できるようにするためにはどこをどう直したらいいのか考えている。

もちろん、これらの Unicode のめんどくさい部分が関わる個別の箇所に個別の処理を書き加えるのは正しくない。それらを統一的に処理するクラスなり関数を設けて、個別の部分では単にそれを利用するだけにしたい。

ここで、1 つのクラスを作ってみたい。このクラスは文字列を引数に取り、サロゲートペアを解決しつつ UCS4 のコードポイントの配列を生成し、さらにそれを書記素クラスタで分割する。このクラスは String に似たメソッドを持ち、String を操作するように書記素クラスタの配列を操作することができる。

例えば Z͑ͫ̓ͪ̂ͫ̽͏̴̙̤̞͉͚̯̞̠͍A̴̵̜̰͔ͫ͗͢L̠ͨͧͩ͘G̴̻͈͍͔̹̑͗̎̅͛́Ǫ̵̹̻̝̳͂̌̌͘!͖̬̰̙̗̿̋ͥͥ̂ͣ̐́́͜͞ という文字列は UTF-16 の文字が 75 個も並んでる複雑なシーケンスだ。この中から G の部分だけ抜き出す、だとか、「ユーザが認識する文字」の数、つまり 6 を得ると言った処理は、String に対する操作では不可能なのだが、このクラスを使うことで


var us = new Unistring("Z͑ͫ̓ͪ̂ͫ̽͏̴̙̤̞͉͚̯̞̠͍A̴̵̜̰͔ͫ͗͢L̠ͨͧͩ͘G̴̻͈͍͔̹̑͗̎̅͛́Ǫ̵̹̻̝̳͂̌̌͘!͖̬̰̙̗̿̋ͥͥ̂ͣ̐́́͜͞");
us.length; // 6 が返る
us.substr(3, 1).toString(); // G̴̻͈͍͔̹̑͗̎̅͛́ が返る
us.charAt(3); // G が返る

と言ったように簡単に操作できるようになる。

というわけで、書いてみた。

実は、UTF-16 シーケンスを書記素クラスタに分割する javascript のライブラリというのはすでにあるのだが(ZALGO! もこのライブラリのドキュメントから取った)、純粋に分割するだけで String に似たメソッド群は提供してくれなかった。

a Surrogation

赤福プラスにおいて、絵文字を画像で表示する処理を追加したのだけど。

この絵文字というものはだいたいのところ、U+FFFF を超えるコードポイントを持っている。これはかなり面食らう。個人的には BMP を超える文字なんて誰が使うのかしら…などと高をくくっていたのである。しかし絵文字なんてキャッチーなものが収録され始めているわけで、ちゃんとやらないとこれは不味いのではないか? と不安になってきた。

ところが、wasavi ではこのへんの Unicode の異様にめんどくさい部分、つまりサロゲートペアと書記素クラスタの扱いはまだ一切何も考えていないのであった(更に輪をかけてめんどくさい bidi もだが)。しかしこれをちゃんとするとなると結構な大改造になる。どうしよう。

基本的には、バッファの内容の保持とそれを操作する機能は Buffer クラスが一元的に持っている。従って直すとしたらそれが主な対象になるのだけど、全てというわけではないので地道に探していじっていく他にない。

cfx to jpm #3

wasavi も 0.6.580 から jpm でビルドするようにした。ただこれらのツールは、ビルド時だけではなく実行時にも影響を及ぼす。大昔は Add on SDK のライブラリは個々の拡張に同梱されていたが(このため wasavi でも Firefox 版だけやたらサイズがでかくなるという問題がかつてあった)最近は SDK のライブラリは Firefox 自身が保持するようになっている。困ったことにこのライブラリが、cfx でビルドされたかあるいは jpm かで微妙に動作を変える。

たとえば cfx の [cci]require()[/cci] に比べて jpm のそれはより commonjs に準拠しているようになっていて、cfx では基準のディレクトリが常に lib なのだが(正確には、エントリポイントスクリプトの dirname かもしれない。未確認)、jpm では require() を実行したソースが位置するパスが基準になるのである。つまり lib/foo とか lib/bar とかだったりと可変なのだ。

これで何が困るのかといえば、wasavi や akahukuplus はソースの共通化のために require() の polyfill を定義しているのだが、ある関数を呼び出した際にその呼び出し元のソースファイルのパスを得るという標準的な方法がないことだ。

標準的な方法がないということは、標準的ではない方法を使わざるを得ないということで、具体的には [cci](new Error).stack[/cci] が返す文字列を取得して解析するしかない。しかしこれは非常に脆弱で、各ブラウザベンダがこのプロパティが返す文字列の内容をちょっとでも変えたら即破綻する。文字列ではなく、もっと構造化されたオブジェクトでスタックフレーム情報を返してくれればもうちょっとましなのだけど…。

さて、cfx から jpm への移行で最後に残ったのは赤福プラスだ。これも移行してみた。また、最近は絵文字が unicode のコードポイントにやたら導入されている状況を鑑みたり鑑みなかったりしつつ、コメント中の絵文字は twitter のそれと同様の画像で置き換えるようにしてみた。

akahukuplus-emoji

wasavi/0.6.580 released

リリースした。

Chrome: https://chrome.google.com/webstore/detail/wasavi/dgogifpkoilgiofhhhodbodcfgomelhe

Opera: https://addons.opera.com/ja/extensions/details/wasavi/

Firefox: https://github.com/akahuku/wasavi/raw/master/dist/wasavi.xpi

* * *

w/W/b/B コマンドがある。これらは vi 内の独自の文字の分類に従ってカーソルを移動させる。これ、Unicode における単語境界の仕様に準拠してもいいんじゃないかなあという気になりつつある。

cfx to jpm #2

などといいつつ、jpm の動作をいろいろ見ている。

cfx と同様、[cci]jpm xpi[/cci] とすることで xpi を生成することができる。ただし、[cci]–pkgdir[/cci] オプションや [cci]–update-link[/cci] オプションがどういう訳か削除されている。前者は、xpi をアーカイブする際のベースディレクトリを指定する。これがないということになっても、単に [cci]cd && jpm xpi[/cci] などとすればよいのでそれほど問題ではない。しかし後者が問題だ。

[cci]cfx xpi[/cci] においては以下のオプションが有効で:

  • –update-link: 拡張を自動更新する際の最新の xpi ファイルの場所を指定する。このオプションを指定すると、cfx は update.rdf を生成する
  • –update-url: 拡張を自動更新する際の更新情報を示す RDF ファイルの場所を指定する。この情報は xpi 内の install.rdf に埋め込まれる

この違いはとてもわかりにくい。前者は [cci]–location-of-latest-xpi[/cci]、後者は [cci]–location-of-update-manifest-rdf[/cci] などと脳内で変換する必要がある。

で。jpm では前述の通り [cci]–update-link[/cci] オプションが削除されている。従って、jpm は update.rdf を一切生成しない。そのため jpm ベースの開発サイクルでは update.rdf を自前で生成しなければならなくなっている。なぜこのような仕様変更が行われたのかはよく分からないが、自前で update.rdf を制御する場面というのは、つまり拡張を AMO 以外の場所で配布する状況ということだ。AMO に載せるのなら気にする必要はない。つまり update.rdf の生成機能が取り除かれているのは野良拡張の配布をやめろという暗黙的な圧力なのかもしれない。

この手の悪意に満ちた嫌がらせはこれだけではない。拡張に自前で署名を施すためのツールとして McCoy というものがあるのだが、これもあからさまに使いにくい。そして、「使いにくいけど将来的には直すので期待してね!」なんつったりしちゃったりしてるのだが、もちろん今現在になっても使いやすくなってはいない。

一方で Mozilla は

インストールを Mozilla の配布チャンネルに限定することは余計な制約であると私たちは考えます

とかなんとか言っている。なーんか、言ってることとやってることが全然違いますね。こんなことばかりやってると、加速度的に信用を失うと思うんですけど。

それはさておき、update.rdf のテンプレートから任意の箇所を package.json の値で置換するような適当な javascript のスクリプトを書いた。javascript で書いたのは、jpm 自体が node.js で動かすスクリプトなのでそれに合わせて。

テンプレートとしてこういうものを用意して






  • {{version}}


    {ec8030f7-c20a-464f-9b0e-13a3a9e97384}
    {{engines.firefox.minVersion}}
    {{engines.firefox.maxVersion}}
    https://example.org/example.xpi






  • package.json とテンプレートを両方読んで、プレースホルダ({{〜}} の部分)を package.json の該当する値で埋める。package.json に値が存在しなければ、プレースホルダを含む要素自体を空に置き換えるようにした。

    とりあえず webliopane fx40fixed を jpm でビルドするようにしてみた。

    a critical bug

    wasavi をリリースしたばかりなのだが、Chrome 上のとある条件下でフリーズするという致命的なバグを直したので今月末辺りにまたリリースするかもしれない。フリーズと言ってもプロセッサのリソースを食いつぶすとかそういう類ではない。

    これは 2 つのあまり関係なさそうな事柄が関係している。

    まず、wasavi が保持するバッファは、各行の末尾に [cci]\n[/cci] の内容を持つテキストノードを保持している。これは、編集後に textarea に代入するために wasavi のバッファから単一の文字列を得る際、単にバッファの div 要素の textContent プロパティを参照するだけで済むようにするためだ。

    次に、wasavi の input モードは div 要素の contentEditable 属性をオンにすることで実現している。

    この 2 つの事柄が Chrome 上で絡みあうと不思議なことが起きる。IME を通した仮入力が最終的に空文字になり、かつカーソル行の div 要素の内容も空になった場合(たとえば空行で仮入力を開始したが、結局全部 backspace で消して仮入力を抜けた時…など)、前述の改行要素が Chrome によって勝手に削除されてしまうようなのだ。

    wasavi のあらゆる編集機能はこの改行要素が存在することを前提としているので、ないとなるとあらゆるところでエラーが発生して満足に動かなくなる。

    weblioPane fixed

    Firefox の拡張に weblioPane というのがあって便利に使っていたのだが、最近になって(おそらく Firefox 40 のリリース以降)使用できなくなって困っていた。しかしなかなか新しいバージョンに更新される気配もないので勝手に直してみた。

    https://github.com/akahuku/webliopane-fx40fixed

    この拡張が参照している weblio の API についてのドキュメントが欲しいのだけど、見つからなかった。

    WebExtensions…?

    何やら物議をかもしている例の件。自作の拡張にはどういう影響があるのか考えてみる。

    Firefox 版の wasavi や赤福プラスは Addon SDK ベースで作っているのだが、SDK の提供するモジュールをそのまま使っているのではない。抽象的なラッパーを挟んでいる。ラッパーが Chrome と Presto Opera と Firefox の差異を吸収して、アプリケーション内にはブラウザ固有のコードはほとんどない。

    したがって、仮に WebExension ベースに移行するとしても、変更する必要があるのは Firefox 版のラッパーだけだ。そもそも WebExtensions が Chrome の拡張ベースだし、ラッパーも Chrome の拡張をベースにしたインターフェースにしてあるので、変更によりむしろコード量は少なくなるかもしれない。

    一方で、SDK ベースのままにするという選択肢もある。ただ、SDK 自体は廃止は免れるということなのだけれど、それは [cci]require(‘chrome’)[/cci] していないものに限られるという。

    ラッパーはもちろんこれを使っている。

    • XMLHttpRequest – SDK が提供するそれは upload プロパティをサポートしていない
    • FormData – SDK はこれを提供していない
    • Blob – SDK はこれを提供していない

    つまるところ、なぜ [cci]require(‘chrome’)[/cci] を使っているのかといえば、SDK が提供する API が全般的に中途半端だからなのだ。それなのに Cu/Ci の使用を今さら咎められても困るのだが、この辺を何とかしないといけない。

    width of character

    wasavi のステータス行にはカーソル位置の桁・行位置を表示しているのだけど、その桁位置とはなんぞやというと、これはカーソル位置の内部的な行頭からのオフセットをそのまま指しているわけではない。つまり内部的な桁位置と表示上の桁位置は違う。

    まず内部で charWidth という変数があり、これは大元の textarea のフォント関連のスタイリングに基づく、[cci]0[/cci] の文字幅だ。技術的には span 要素に [cci]0[/cci] を入れた際の offsetWidth の値だ。

    それを踏まえて表示上の桁位置を算出するには、span 要素に行頭からカーソルの物理位置までの文字列を入れ、その offsetWidth を出し、charWidth で割る。表示しているフォントが固定幅であればこれで特に問題ない。

    ところが困ったことに、固定幅フォントでさえ、文字列全体の offsetWidth が charWidth の整数倍にならないことが多々あるのであった。つまりこれは、cssom では offsetWidth の型は long だ。しかし実際には、それは小数で保持されているのが原因なんだと思う。そういうわけで正しい表示上の桁位置が得られない。

    うーんどうしよう。

     * * *

    charWidth を算出する際に、100 個並べた [cci]0[/cci] の offsetWidth を 100 で割るようにした。つまり charWidth の精度を上げた。