Orz部屋 - Tips/ja_JP.UTF-8運用 Diff

  • Added parts are displayed like this.
  • Deleted parts are displayed like this.

!LANG=ja_JP.UTF-8による運用
これまで、'''ja_JP.eucJP'''ロケールで運用してきたが、libreofficeやfirefox等の今時のtoolkitで動くソフトウェアは、Unicodeを内部コードとし(これはI18N的に問題ない)、Filesystem namespaceのUTF-8符号化を前提としている(おぃおぃ)ので、non UTF-8なFilesystem運用が面倒になってきたので、'''ja_JP.UTF-8'''ロケールに全面的に乗り換えを実施した。

これに伴う各種問題を纏めておく

!!FDclone(ports/shells/fd)
ファイル・ディレクトリ名をEUC-JPからUTF-8へ変換した環境で、FDcloneの表示が乱れるパス名が存在する

* FDCloneの内部表現がShiftJIS系(実務的にはCP932かその亜種)でUnicodeの全ての文字を扱えないため (内部表現による技術的制約)
* 歴史的経緯に伴うUnicode上での符号位置の運用に混乱(混用)が生じているため (既に作成済みのデータは無くならないよ… Orz)
** Unicode委員会が、出典と異なる表例字体を間違って規格表に収録 (現行版はさすがに修正済みだが、一時期は'''表例字体はあくまでも参考資料で、厳密には規格の一部では無い'''ので規格の不備では無いと…)
*** 既存の規格表から収録したはずのCJK文字群に出典の存在が確認出来ない文字が収録されているという失態もありました(幽霊文字問題)
** フォントベンダーが、Unicode規格表の間違った表例字体を元にフォントグリフをデザインした
** OSベンダーが、Unicodeと既存のエンコーディング変換を作る際に、規格表の符号名・収録元ではなくフォントグリフが似ている文字に変換するテーブルを構築した(そうしないと、表示がバグっているとユーザーから苦情が来るから)
** 結果的に、UCS的に本来の符号位置と異なる符号位置で実運用される文字が発生
** JISX0208に出典を持つ文字の代表的な混用事例 - [[波ダッシュ問題|https://x0213.org/wiki/wiki.cgi?page=%C7%C8%A5%C0%A5%C3%A5%B7%A5%E5%CC%E4%C2%EA]]
* FDCloneの内部表現がCP932に寄せてあるため、EUC-JP → UTF-8で変換したパス名に含まれる一部の符号位置を扱えない
** 当方の環境で問題が出た文字は、WAVE DASH(U+301C)・MINUS SIGN(U+2016)・EM DASH(U+2014)の3種
** 上記のコードポイントに限れば、CP932の代わりに'''Shift_JISX0213'''符号化を採用すれば符号分離出きるはずだが、いずれ別の符号位置で同様の問題が生じる
** EUC-JP→UTF-8移行した環境での運用を想定するのであれば、FDCloneの変換テーブルを修正する方が現実的
* 変換テーブルをUnix向けに修正するパッチ {{attach_anchor(fd.diff)}}
--- shells/fd/Makefile.orig
+++ shells/fd/Makefile
@@ -32,6 +32,20 @@ DOCS_JA=     FAQ \
  
  OPTIONS_DEFINE=        DOCS
  
+
+WAVE_DASH_DESC=        Use WAVE DASH(U+301C) instead of FULLWIDTH TILDE(U+FF5E)
+WAVE_DASH_CFLAGS=-DWAVE_DASH
+OPTIONS_DEFINE+=WAVE_DASH
+
+MINUS_SIGN_DESC=Use MINUS SIGN(U+2212) instead of FULLWIDTH HYPHEN-MINUS(U+FF0D)
+MINUS_SIGN_CFLAGS=-DMINUS_SIGN
+OPTIONS_DEFINE+=MINUS_SIGN
+
+EM_DASH_DESC=Use EM DASH(U+2014) instead of HORIZONTAL BAR(U+2015)
+EM_DASH_CFLAGS=-DEM_DASH
+OPTIONS_DEFINE+=EM_DASH
+
+
  post-install:
         ${INSTALL_DATA} ${WRKSRC}/_fdrc.orig \
                 ${STAGEDIR}${PREFIX}/etc/fd2rc.dist
--- /dev/null  2024-07-05 03:21:02.298084000 +0900
+++ shells/fd/files/patch-mkunitbl.c   2024-07-04 21:43:08.356467000 +0900
@@ -0,0 +1,54 @@
+--- mkunitbl.c.orig   2019-07-27 00:00:00.000000000 +0900
++++ mkunitbl.c        2024-07-04 21:39:45.280042000 +0900
+@@ -167,7 +167,11 @@
+      {0x0451, 0x8476},
+
+      {0x2010, 0x815d},
++#ifdef       EM_DASH
++     {0x2014, 0x815c},
++#else
+      {0x2015, 0x815c},
++#endif
+      {0x2018, 0x8165},
+      {0x2019, 0x8166},
+      {0x201c, 0x8167},
+@@ -217,6 +221,9 @@
+      {0x2208, 0x81b8},
+      {0x220b, 0x81b9},
+      {0x2211, 0x8794},
++#ifdef       MINUS_SIGN
++     {0x2212, 0x817c},
++#endif
+      {0x221a, 0x81e3},
+      {0x221d, 0x81e5},
+      {0x221e, 0x8187},
+@@ -339,6 +346,9 @@
+      {0x3013, 0x81ac},
+      {0x3014, 0x816b},
+      {0x3015, 0x816c},
++#ifdef       WAVE_DASH
++     {0x301c, 0x8160},
++#endif
+      {0x301d, 0x8780},
+      {0x301f, 0x8781},
+      {0x3041, 0x829f},
+@@ -9164,7 +9174,9 @@
+      {0xff0a, 0x8196},
+      {0xff0b, 0x817b},
+      {0xff0c, 0x8143},
++#ifndef      MINUS_SIGN
+      {0xff0d, 0x817c},
++#endif
+      {0xff0e, 0x8144},
+      {0xff0f, 0x815e},
+      {0xff10, 0x824f},
+@@ -9245,7 +9257,9 @@
+      {0xff5b, 0x816f},
+      {0xff5c, 0x8162},
+      {0xff5d, 0x8170},
++#ifndef      WAVE_DASH
+      {0xff5e, 0x8160},
++#endif
+      {0xffe0, 0x8191},
+      {0xffe1, 0x8192},
+      {0xffe2, 0x81ca},

!!kterm(ports/japanease/kterm)
'''kanjiMode=utf-8'''で動くktermへ、XIM経由で日本語入力すると文字化けする

* XIMから受け取るEUC-JP文字列を入出力バッファ向けのUTF-8コーディングに変換する際の実装バグ
** 標準では、XIMは'''ja_JP.eucJP'''ロケールに初期化され、'''Input()''' @ ''input.c''にて、XIMからの入力は'''convEUCtoUTF8()''' @ ''convert.c''によってEUC-JPからUTF-8へ変換され入出力バッファへ書き込まれる
** '''convEUCtoUTF8'''は、JISX0208面に対して'''ucode_kanji1[(c1-33)*94 + (e2-33)]'''でJISコードに対するUnicodeを拾うが、'''e2 == c2 | 0x80'''なので '''128'''オフセット後方の符号を拾っている
*** 内部処理に使うCompound stringとUTF-8の相互変換では、'''ucode_kanji1'''を正しく参照している
** '''convEUCtoUTF8'''を修正するパッチ {{attach_anchor(ja-kterm-EUCtoUTF8.diff)}}
--- /dev/null  2024-06-13 20:59:11.878039000 +0900
+++ japanese/kterm/files/patch-convert.c       2024-06-13 13:06:05.265359000 +0900
@@ -0,0 +1,11 @@
+--- convert.c.orig    2016-11-05 06:41:21.000000000 +0900
++++ convert.c 2024-06-13 12:50:40.353827000 +0900
+@@ -1106,7 +1106,7 @@
+                      if (e2 = *es++) {
+                              int c1 = e1 & ~0x80;
+                              int c2 = e2 & ~0x80;
+-                             int ucode = ucode_kanji1[(c1-33)*94 + (e2-33)];
++                             int ucode = ucode_kanji1[(c1-33)*94 + (c2-33)];
+                              int len = utf8_len(ucode);
+
+                              if (ucode == U_error) {

* 別解として、'''eucJPLocale'''リソースを操作するとXIMとの通信をUTF-8化でき、'''Input()''' @ ''input.c''で'''EUC_KANJI'''の代わりに'''UTF8_KANJI'''時に無変換にすれば、UTF-8でそのまま入力可能になるが、kterm自身の表示処理はCompound stringで処理されるので、表現力は増えない

'''タイトルにCJK文字が表示出来ない'''

* '''titleEncoding'''リソースに'''UTF8_STRING'''を設定する
** kterm本体側ではなく(おそらく)libXt側で用意されるリソース (設定しないと、CJK文字が白抜けになる)
** ''.Xresources''に'''KTerm*titleEncoding: UTF8_STRING'''を追加
* ''cwdcmd''エイリアスで使うタイトルを書き換えるエスケープシーケンス処理('''do_osc'''@''misc.c'')に8bit目が立っていると、問答無用で'''?'''へ置き換えるコードが入っている
** UTF-8運用前提で、制御文字以外はすべて通すパッチ {{attach_anchor(ja-kterm-titleCJK.diff)}}
*** EUC・JIS・SJISをサポートするなら前項と併せて、もう少し真面目に実装する必要がある
--- /dev/null   2024-06-27 10:27:19.126058000 +0900
+++ japanese/kterm/files/patch-misc.c   2024-06-17 01:00:45.000000000 +0900
@@ -0,0 +1,11 @@
+--- misc.c.orig        2016-11-05 06:41:21.000000000 +0900
++++ misc.c     2024-06-17 00:59:35.008041000 +0900
+@@ -775,7 +775,7 @@
+               mode = 10 * mode + (c - '0');
+       if (c != ';') okay = False;
+       cp = buf;
+-      while(isprint((c = (*func)(), c = (c >= 0x80) ?'?' :c)) && cp < bufend)
++      while(isprint((c = (*func)(), (c >= 0x80) ?'?' :c)) && cp < bufend)
+               *cp++ = c;
+       if (c != 7) okay = False;
+       *cp = 0;

!!libX11(ports/x11/libX11)
U+FF3C(FULLWIDTH REVERSE SOLIDUS)がEUC-JP localeなXIM入力で消失する (例 kterm + ibus-anthy)

* UTF-8 localeだと問題なく受け取れる (例 utf-8 modeなxterm + ibus-anthy)
* libX11内部のUCS-4→JISX0208変換テーブルでU+FF3Cが無効文字扱いになっているため (代わりに、U+005C → 0x2140が入っている)
** 一方、JISX0208→UCS-4変換テーブルでは0x2140 → U+FF3Cへマッピングしている (JISX0208 → UCS-4 → JISX0208でラウンドトリップ出来てない)
* 変換テーブルにU+FF3C → 0x2140を追加するパッチ {{attach_anchor(libX11.diff)}}
--- /dev/null   2024-07-05 03:18:10.763826000 +0900
+++ x11/libX11/files/patch-src_xlibi18n_lcUniConv_jisx0208.h    2024-07-05 02:46:34.465009000 +0900
@@ -0,0 +1,35 @@
+--- src/xlibi18n/lcUniConv/jisx0208.h.orig     2024-04-06 08:02:05.000000000 +0900
++++ src/xlibi18n/lcUniConv/jisx0208.h  2024-07-05 02:43:27.738752000 +0900
+@@ -1019,7 +1019,7 @@
+   return RET_ILSEQ;
+ }
+
+-static const unsigned short jisx0208_2charset[6879] = {
++static const unsigned short jisx0208_2charset[6880] = {
+   0x2140, 0x2171, 0x2172, 0x2178, 0x212f, 0x224c, 0x216b, 0x215e,
+   0x212d, 0x2279, 0x215f, 0x2160, 0x2621, 0x2622, 0x2623, 0x2624,
+   0x2625, 0x2626, 0x2627, 0x2628, 0x2629, 0x262a, 0x262b, 0x262c,
+@@ -1875,7 +1875,7 @@
+   0x2177, 0x2341, 0x2342, 0x2343, 0x2344, 0x2345, 0x2346, 0x2347,
+   0x2348, 0x2349, 0x234a, 0x234b, 0x234c, 0x234d, 0x234e, 0x234f,
+   0x2350, 0x2351, 0x2352, 0x2353, 0x2354, 0x2355, 0x2356, 0x2357,
+-  0x2358, 0x2359, 0x235a, 0x214e, 0x214f, 0x2130, 0x2132, 0x212e,
++  0x2358, 0x2359, 0x235a, 0x214e, 0x2140, 0x214f, 0x2130, 0x2132, 0x212e,
+   0x2361, 0x2362, 0x2363, 0x2364, 0x2365, 0x2366, 0x2367, 0x2368,
+   0x2369, 0x236a, 0x236b, 0x236c, 0x236d, 0x236e, 0x236f, 0x2370,
+   0x2371, 0x2372, 0x2373, 0x2374, 0x2375, 0x2376, 0x2377, 0x2378,
+@@ -2348,10 +2348,10 @@
+ };
+ static const Summary16 jisx0208_uni2indx_pageff[15] = {
+   /* 0xff00 */
+-  { 6788, 0xdf7a }, { 6800, 0xffff }, { 6816, 0xffff }, { 6832, 0xefff },
+-  { 6847, 0xffff }, { 6863, 0x3fff }, { 6877, 0x0000 }, { 6877, 0x0000 },
+-  { 6877, 0x0000 }, { 6877, 0x0000 }, { 6877, 0x0000 }, { 6877, 0x0000 },
+-  { 6877, 0x0000 }, { 6877, 0x0000 }, { 6877, 0x0028 },
++  { 6788, 0xdf7a }, { 6800, 0xffff }, { 6816, 0xffff }, { 6832, 0xffff },
++  { 6848, 0xffff }, { 6864, 0x3fff }, { 6878, 0x0000 }, { 6878, 0x0000 },
++  { 6878, 0x0000 }, { 6878, 0x0000 }, { 6878, 0x0000 }, { 6878, 0x0000 },
++  { 6878, 0x0000 }, { 6878, 0x0000 }, { 6878, 0x0028 },
+ };
+
+ static int

!!xterm(ports/x11/xterm)
* '''utf8=true'''で適切なフォントを指定すれば、普通に動く
* XIMで'''OverTheSpot'''で入力するには、'''ximFont'''リソースを設定する必要がある
* '''utf8Fonts.font'''リソースにISO10646フォントを設定した場合、グリフが未実装のコードポイントは空白や豆腐になる
** これが原因で、未だに'''kterm'''が手放せない

!!ja-ibus-anthy(ports/japanese/ibus-anthy)
ibus-anthyを使ったXIM入力でU+007eの全角として、U+301C(WAVE DASH)ではなくU+FF5E(FULLWIDTH TILDE)が入力される

一方、emacsの'''japanese-anthy''' input modeは、U+301C(WIVE DASH)になっている

* ibut-anthy内の変換テーブルをU+301C(WAVE DASH)側に調整するパッチ {{attach_anchor(ja-ibus-anthy.diff)}}
--- japanese/ibus-anthy/Makefile.orig
+++ japanese/ibus-anthy/Makefile
@@ -21,4 +21,13 @@ USES=                gettext gmake gnome libtool pkgconfig python
  USE_GNOME=     pygobject3
  GLIB_SCHEMAS=  org.freedesktop.ibus.engine.anthy.gschema.xml
  
+OPTIONS_DEFINE+=WAVE_DASH
+WAVE_DASH_DESC= Use WAVE DASH(U+301C) instead of FULLWIDTH TILDE(U+FF5E)
+WAVE_DASH_FILES=engine/python2/tables.py engine/python3/tables.py engine/python2/thumb.py engine/python3/thumb.py data/org.freedesktop.ibus.engine.anthy.gschema.xml.in
+
+post-patch-WAVE_DASH-on:
+       for _f in ${WAVE_DASH_FILES}; do \
+               ${REINPLACE_CMD} -e 's/~/〜/g' -e 's/uff5e/u301c/g'  "${WRKSRC}/$${_f}" ; \
+       done
+
  .include <bsd.port.mk>

!!Citrus iconv(lib/libiconv_modules, share/i18n/esdb)
Samba等でMicrosoft Windows側のUCS符号運用を許容しつつ、FreeBSD側でEUC-JPと互換性のあるUTF-8を運用するためja_JP.eucJP locale時に'''unix charset'''に指定していたEUC-JP-MSに相当するUTF-8エンコーディングの亜種が欲しい

* Citrus的には、符号位置変換なのでCSIDを新設すべきかも知れないが、UTF8へのmultibyte符号化の亜種として'''UTF-8-MS'''を手抜き実装した (邪道)
** CSIDを独立に定義しても、UTF-8系の運用しか想定してないので、独立定義するメリットが薄い
** 数文字の符号位置変更以外ほぼ恒等写像なUCS全体をカバーする変換マップの格納効率が悪すぎる
* UTF-8-MSを追加するパッチ {{attach_anchor(citrus-iconv.diff)}}
** ''lib/libiconv_modules/UTF8/citrus_utf8.c''
*** UTF1632の実装を参考に、'''_UTF8EncodingInfo'''型にモードフラグ実装
*** UTF1632の実装を参考に、'''_citrus_UTF8_encoding_module_init'''にVARIABLEデコードと'''_UTF8EncodingInfo'''のモードフラグ初期化を実装
*** Microsoft側とUnix側で入れ替えたいコードポイントを変換するヘルパー関数を実装
*** '''_citrus_UTF8_wcrtomb_priv'''先頭に、モードフラグ依存で'''wc'''を変換するコードを追加
*** '''_citrus_UTF8_mbrtowc_priv'''で'''wchar'''を書き戻す直前に、モードフラグ依存で'''wchar'''を変換するコードを追加
** ''share/i18n/esdb/UTF/Makefile'' - UTF-8-MSのパラメータ定義
** ''share/i18n/esdb/UTF/UTF.part'' - UTF-8-MSの追加
** ''share/i18n/esdb/UTF/UTF.alias'' - UTF-8-MSの別名追加
* ''smb4.conf''に'''unix coding = UTF-8-MS'''で指定すると、Windows側からMicrosoftなUCS符号位置での参照をUnicode本来の符号位置で運用出来る

!!Kernel iconv(sys/libkern/iconv_ucs.c, lib/libkiconv/quirks.c)
ja_JP.eucJP locale時代と同様に、msdosfs mount時にMicrosoftの変則UCSな日本語ファイル名のUCSコードポイントを正しくデコードしたい

* Kernel iconvの実装には、mount_msdosfsからlibkiconv経由でiconv(3)で生成した変換マップをKernel側にアップロードする仕組みがあるが、UTF-8のような3byte以上のシーケンスをサポートしない(この部分は、実質 2byte文字専用)
** 3byte文字サポートを有効にするマクロがあるが、メンテナンスされているか微妙… (UTF-8だと4byteのケースもありえる)
** 実は、UTF-16BE・UTF-8サポートは、''sys/libkern/iconv_ucs.c''側でハードコードされている(iconv_xlat16.cで扱ってない)
*** 変換マップのエクスポートは、初期化子'''iconv_ucs_init'''から'''iconv_add'''で登録している
** 前出のCitrus iconvの改造に習い、UTF-8の亜種としてねじ込む
* UTF-8-MSサポートを追加するパッチ {{attach_anchor(libkern-iconv.diff)}}
* UTF-8-MS quirksを追加するパッチ {{attach_anchor(libkiconv-quirks.diff)}}
* '''msdosfs''' mountの'''-L''' optionに'''ja_JP.UTF-8'''を付けると、Microsoftの変則UCSなファイル名を普通のUTF-8で見えるようになるはず

!! FUSE NTFS(ports/sysutils/fusefs-ntfs)
NTFS mount時にMicrosoftの変則UCSな日本語ファイル名のUCSコードポイントを正しくデコードしたい

* '''use_utf8'''モード時(デフォルト)の、NTFS内部コード(UTF-16LE)とUTF-8の相互変換は''libntfs-3g/unistr.c''に自前の実装を持っている
** NTFS内部コード側は、原則MicrosoftなUCS符号位置で運用されているので、''unistr.c''に換字テーブルを組み込み、'''ntfs_utf16_to_utf8'''・'''ntfs_utf8_to_utf16'''で問題となるUCS符号位置を修正
* MicrosoftなUCS符号位置を差し替えるパッチ {{attach_anchor(fusefs-ntfs.diff)}}

!!ee(src/contrib/ee)
日本語混じりの行にて'''Ctrl+E'''でカーソルを行末に移動されると、実際の行末より後ろにカーソルが飛んでいく

* ee自身は、多バイト文字に対応していない (2024/06/13現在)
* 多バイト文字で、バイト数と画面上のグリフ幅が一致しないため
** EUC-JPの典型的な多バイト文字は、2バイト文字かつワイド文字なので、1バイト1文字でカーソル位置を計算しても実務上大きな矛盾が起きない
** UTF-8符号化でJISX0208文字は、典型的には3バイト表現なので、カーソル位置の計算がずれる