井汲です。twmode で、URL やタグについたハイパーリンクや色の場所がずれ
ることがあって、その原因と対処法を報告します。
時折 RT でハイパーリンクや色の場所がずれたツイートが流れてくることがあっ
て、そういうのは決まって凝った顔文字が使われていました。何か unicode が
らみで普段あまり使わないような文字が含まれると変なことになるようだな、と
当たりをつけていたのですが、先日ちょっと余裕があるときにまたその現象が起
きたので当該のツイートを捕捉して色々調べることができました。その結果、現
象を再現可能な顔文字の抽出に成功しました。これです:
http://twitter.com/ikumikeita/status/610472271916855296
現行の twmode だと、URL についた下線・ハイパーリンクがずれているのがわか
ると思います。
(以下、unicode 正規化については、ここ数日で web 検索で仕入れただけの付
け焼き刃の知識で書いているので、誤りや不備等あるかもしれません。まずい点
があったらご指摘ください)
ずれが発生する原因は、twmode が使っている unicode 正規化での文字数の変
化の評価に、ちょっと不十分な点があることです。文字数の変化に合わせて下線
や色等の text property を付ける位置を補正する必要があって、その補正値を
計算しているのが関数 twittering-make-gap-list なのですが、その出だしはこ
うなっています。
----------------------------------------------------------------------
(let ((result nil)
(regexp
(if (require 'ucs-normalize nil t)
(concat "\\(?:\\([<>]\\)\\|\\("
ucs-normalize-combining-chars-regexp "\\)\\)")
"\\([<>]\\)"))
(pos 0)
(gap 0))
(while (string-match regexp text pos)
(let ((shift (if (match-beginning 1)
3
1)))
----------------------------------------------------------------------
この一番最後で、if 文の else 節が返す値を 1 で決め打ちにしているのがまず
い点です。この返り値は、正規化を行っている関数
twittering-normalize-string の実体 ucs-normalize-NFC-string が
「結合文字がある場合に、正規化で結合した結果減らす字数」と一致するべき値
です。
「ひらがな・カタカナ」+「結合用濁点・半濁点」のような場合は確かにちょ
うど1文字減るのですが、上の例の顔文字ではそうなりません。結合の前後で文
字数に変化がなく、「結合した結果減る字数」は 1 ではなく 0 です。このずれ
が、text property の位置のずれとなって現れています。
上の例の顔文字がなぜそんなことになるのかと言うと、「点の下に丸が3つぶ
ら下がっている」という絵面を表現するために、顔文字の開発者は「半角カナ」
の中黒に unicode の結合文字を3つ続けるということをしているのですが、
「それを結合した結果の1文字」というのが存在しないため、正規化結合を行っ
ても結局正規化前の文字列がそのまま残されている、ということになっているわ
けです。
このような「絵面を再現するためだけに結合文字を使い、実際にそれを結合し
た結果の1文字というのが存在するかどうかはまったく気にしない」というのは、
凝った顔文字ではおそらくしょっちゅう使われているやり方でしょう。
また、http://nomenclator.la.coocan.jp/unicode/normalization.htm を見る
と、アルファベットの母音にアクセント記号等の結合文字が2つ以上続いたもの
が正規化結合の結果1文字にまとまるということも普通にあるようなので、そう
いう場合は逆に「正規化で減る文字数」は 1 よりも大きくなるはずです。したがっ
て、その場合も twmode では text property の位置がずれると予想されます。
以下、この問題の対処法を2通り述べます。
[その1]
正規化で減る文字数を1ヶ所毎に計算するようにしてみたのが添付パッチ1本
目の修正です。この修正で、私の所ではずれは出ないようになりました(正規化
を行う範囲を、結合文字の連続が始まる直前の1文字からと決めつけている所が
ちょっと自信なし)。
さて、この問題を調べている間に、unicode 正規化には別の副作用があること
を知りました。twmode が使っている NFC 正規化では、
http://tama-san.com/unicode%E3%81%AE%E5%90%88%E6%88%90%E9%99%A4%E5%A4%96%E3%81%A8hfs%E3%81%AE%E6%AD%A3%E8%A6%8F%E5%8C%96/
にあるように、漢字によっては別の字形の文字に変わってしまうことがあるよう
です。これも実例を作ってみました:
http://twitter.com/ikumikeita/status/611531576241815554
ここに並んでいる2つの「神」という文字は、現行の twmode で見ると同じ文字
に見えるはずですが、元々は1つ目の方はしめすへんが「示」の方の字で、2つ
目の方が普通の「ネ」の方の字です。twmode が行っている NFC 正規化を通じて、
後者が前者に化けてしまっているという状況です。
上に掲げたページで、mac のファイルシステム HFS+ で使われているという
「Modified NFD」に基づく正規化は、emacs 上では
ucs-normalize-HFS-{NFD,NFC}-* という関数として提供されているようなので、
twmode でもこれを使うと文字化けの問題は解消します(よくわかっていないの
で、完全に解消するわけではなくて「緩和」なのかもしれませんが)。添付パッ
チ2本目の修正を追加することで、手元では2種類の「神」が別々に表示される
ようになりました。
(ただ、このタイプの文字化けで変身してしまう文字は、unicode の規格的には
非推奨のものらしく、もしかしたら同一性を気にせず積極的に別の文字に変えて
しまった方がむしろ望ましい類のものかもしれません)
[その2]
ここまで述べてきた問題は、すべて unicode 正規化に起因するもので、その
処理が twmode に入ったのは 2014/4/21 の
f139102d02afb60c937b19c300d90fcd134d438f
の commit でした。添付パッチを使う代わりに、この commit を元に戻すことで
も、
o text property の位置のずれ
o 正規化に起因する文字化け
は共に解消するはずです。この commit を必要とした理由が私にはわからなかっ
たので、疑問をつぶやいたところ、松尾さんが次のように教えてくださいました:
http://twitter.com/cvmat/status/611352469893808128
----------------------------------------------------------------------
@ikumikeita 問題はテキスト端末上の表示です。テキスト端末では合成文字をうまく
表示できず、合成文字以後の表示が乱れます。また、表示上は合成されても「幅0の
文字」が残るので、そこにカーソルが合う問題もあります。
----------------------------------------------------------------------
http://twitter.com/cvmat/status/611547264809447425
----------------------------------------------------------------------
… 添付の画像(それぞれxterm, GNU screen)のように末尾が欠けたり余計な文字
が表示されたりします。 http://t.co/bgHhCc4AUa
----------------------------------------------------------------------
この文字端末上の問題についての私の考えは、次のように進んで結論が出てい
ません。
(1) これだと「twmode だけ」が対応していることになるので、別の経路から
emacs 内に「分離した濁点・半濁点」が入ってくると、この文字端末上で同
様の問題が起きることに変わりはない。
(2) だとすると、twmode の側で対処するというのはあんまり筋がいい解決法で
はなくて、本来なら「分離した濁点・半濁点がどこから入ってきても、自分
の端末で適切に扱える」ような設定を別個に行うのが筋なのではないか。
(3) とは言え、それを実際にきちんと行うのは結構難しそうだ。(表示だけなら
display table や terminal coding system の細工で何とかなるかもしれな
いが、「幅0の文字」にカーソルが合ってしまうという件はそれでは回避で
きないだろう)
(4) となると結局、現行のように twmode が正規化合成を行うのが全体のコスト
を最小に抑える現実的な対処法かもしれない。実際、私も「凝った顔文字」
なんてものが流入してくる経路は現状 twmode しかないような使い方をして
いるし。
という訳で、対処法その1、その2はどちらがいいのか自分の中で結論が出ま
せんでした。両論併記という形で報告を締めくくります。
井汲 景太
diff -r 069a8e01367c twittering-mode.el
--- a/twittering-mode.el Mon Apr 27 03:09:53 2015 +0900
+++ b/twittering-mode.el Sat Jun 20 22:56:27 2015 +0900
@@ -7288,7 +7288,16 @@
(while (string-match regexp text pos)
(let ((shift (if (match-beginning 1)
3
- 1)))
+ ;; 合成後に1文字にまとまるとは限らないので、
+ ;; 実際に合成してみた文字数と比較してみる。
+ (save-match-data ; ucs-normalize-*-string は match data を壊す。
+ (let ((str (substring text
+ ;; (1- (match-beginning 2))
+ ;; text が合成文字から始まってる
+ ;; 場合は 1- しちゃまずい。
+ (max (1- (match-beginning 2)) 0)
+ (match-end 2))))
+ (- (length str) (length (twittering-normalize-string str))))))))
(setq result
(cons `(,(+ gap (match-end 0)) . ,(+ gap shift)) result))
(setq gap (+ shift gap)))
diff -r e770e8adfb37 twittering-mode.el
--- a/twittering-mode.el Wed Jun 17 01:02:02 2015 +0900
+++ b/twittering-mode.el Sat Jun 20 23:35:29 2015 +0900
@@ -995,7 +995,7 @@
(defun twittering-normalize-string (str)
(if (require 'ucs-normalize nil t)
- (ucs-normalize-NFC-string str)
+ (ucs-normalize-HFS-NFC-string str)
str))
;;;;
------------------------------------------------------------------------------
_______________________________________________
twmode-users mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/twmode-users