XIMFeedbackの仕様解釈と実装

XIMFeedbackの仕様には不明瞭な点が多い(XIM仕様そのものもそうだが)。いろいろな開発者が独自に解釈したために、今やさまざまな(XIMクライアントの)実装が存在する。そのうち、わりとマシな仕様解釈と、実装を紹介したい。

OpenOffice

ここではOpenOffice 1.1.0 RC4 (645) のoo_645_src/vcl/unx/source/app/i18n_cb.cxxを元に説明する★1。このファイルの中の関数Preedit_FeedbackToSAL()が、XIMFeedbackの値を解釈している。

// Convert the XIM feedback values into appropriate VCL
// SAL_EXTTEXTINPUT_ATTR values
// returns an allocate list of attributes, which must be freed by caller
USHORT*
Preedit_FeedbackToSAL ( XIMFeedback* pfeedback, int nlength )
{
    USHORT *psalattr;
    USHORT  nval;
    USHORT  noldval = 0;
    XIMFeedback nfeedback;

    // only work with reasonable length
    if (nlength < 0)
        psalattr = (USHORT*)malloc(nlength * sizeof(USHORT));
    else
        return (USHORT*)NULL;

    for (int npos = 0; npos < nlength; npos++)
    {
        nval = 0;
        nfeedback = pfeedback[npos];

        // means to use the feedback of the previous char
        if (nfeedback == 0)
        {
            nval = noldval;
        }
        // convert feedback to attributes
        else
        {
            if (nfeedback & XIMReverse)
                nval |= SAL_EXTTEXTINPUT_ATTR_HIGHLIGHT;
            if (nfeedback & XIMUnderline)
                nval |= SAL_EXTTEXTINPUT_ATTR_UNDERLINE;
            if (nfeedback & XIMHighlight)
                nval |= SAL_EXTTEXTINPUT_ATTR_HIGHLIGHT;
            if (nfeedback & XIMPrimary)
                nval |= SAL_EXTTEXTINPUT_ATTR_DOTTEDUNDERLINE;
            if (nfeedback & XIMSecondary)
                nval |= SAL_EXTTEXTINPUT_ATTR_DASHDOTUNDERLINE;
            if (nfeedback & XIMTertiary) // same as 2ery
                nval |= SAL_EXTTEXTINPUT_ATTR_DASHDOTUNDERLINE;

#if 0 // visibility feedback not supported now
            if (   (nfeedback & XIMVisibleToForward)
                || (nfeedback & XIMVisibleToBackward)
                || (nfeedback & XIMVisibleCenter) )
            { }
#endif
        }
        // copy in list
        psalattr[npos] = nval;
        noldval = nval;
    }
    // return list of sal attributes
    return psalattr;
}

この実装の興味深い点は次の通りである。

  • XIMFeedbackの値(pfeedback[npos])が0の場合は特別であり、直前のフィードバックと同じ値をもつ。
  • XIMFeedbackの値をビットマスクとして扱う。
  • 1.0.3と解釈が異なる。

以下、順に説明する。

XIMFeedbackの値が0のときの振る舞い

例えば、「今日は晴れです」("今日は" は下線、"晴れです" は反転文字)を描画する場合、XIMFeedbackの配列は通常次のような値をもつ。

2221111

しかし、OpenOfficeではXIMFeedbackの値が0の場合は直前のフィードバックと同じ値を使用するので、次のような値でも同様な結果が得られるということである。

2001000

他の多くのXIMクライアントでは、この場合「日はれです」("今" は下線、"晴" は反転文字、そのほかは通常文字)と表示するはずである。XIMFeedbackの値が0のときの解釈は、通常表示と同様に(下線や反転などの属性なしで)描画するという解釈が自然ではないだろうか。

XIMFeedbackの値をビットマスクとして扱う

XIMFeedbackの値と文字属性の対応は次の通りであり、さらに下線と反転の文字属性はビットマスクで同時に指定できる。

XIMFeedbackの値 文字属性
0 直前の文字の文字属性を使用
XIMReverse 反転(前景色が白、背景色が青)
XIMUnderline 下線(実線)
XIMHighlight 反転(前景色が白、背景色が青)
XIMPrimary 下線(波線)
XIMSecondary 下線(一点波線)
XIMTertiary 下線(一点波線)

例えば、XIMFeedbackの値が3の場合、下線と反転の両方の属性で表示されることになる。XIM仕様を素直に解釈すると実に自然な気もするが、Mozillaを始め多くのXIMクライアントはこの値をビットマスクとして解釈していない。

そのほか、XIMHighlightXIMReverseが同じ文字属性というのも注意したい点である。

1.0.3と解釈が異なる

1.0.3では次のような(不思議な)対応だった。XIMReverseを波線に割り当てるところが気になる。

XIMFeedbackの値 文字属性
0 直前の文字の文字属性を使用
XIMReverse 下線(波線)
XIMUnderline 下線(実線)
XIMHighlight 反転(前景色が白、背景色が青)

★1 OpenOfficeのソースは膨大だ。頭から読んでたら一生かかりそうな気がする。ちなみに(インデント幅ではなく)タブ幅が4なので、expand -t 4で処理してからの方が読みやすい。

Mozilla

Mozilla 1.4(Firebird 0.6)とMozilla 1.3(Firebird 0.5)では、XIMFeedbackの値の解釈が異なる。関係するソースコードで実装を確認する。

まずはMozilla 1.4のmozilla/widget/src/gtk/nsGtkIMEHelper.cpp(L293)をみる。ここではXIMFeedbackの値を解釈している。

    for (pFeedbackAttr = feedbackAttr;
         pFeedbackAttr < &feedbackAttr[composeUniStringLen];
         pFeedbackAttr++) {
      switch (*preeditFeedback++) {
      case XIMReverse:
        *pFeedbackAttr = NS_TEXTRANGE_SELECTEDRAWTEXT;
        break;
      case XIMUnderline:
        *pFeedbackAttr = NS_TEXTRANGE_CONVERTEDTEXT;
        break;
      default:
        *pFeedbackAttr = NS_TEXTRANGE_SELECTEDCONVERTEDTEXT;
      }
    }

Mozilla 1.3では、この部分は次のようになっていた。

    for (pFeedbackAttr = feedbackAttr;
         pFeedbackAttr < &feedbackAttr[composeUniStringLen];
         pFeedbackAttr++) {
      switch (*preeditFeedback++) {
      case XIMReverse:
        *pFeedbackAttr = NS_TEXTRANGE_SELECTEDRAWTEXT;
        break;
      case XIMUnderline:
        *pFeedbackAttr = NS_TEXTRANGE_CONVERTEDTEXT;
        break;
      case XIMHighlight:
        *pFeedbackAttr = NS_TEXTRANGE_SELECTEDCONVERTEDTEXT;
        break;
      default:
        *pFeedbackAttr = NS_TEXTRANGE_RAWINPUT;
      }
    }

ご覧の通り、XIMFeedbackの値はビットマスクとして扱われていない。NS_TEXTRANGE_...の値の意味については後述することにして、結果的に次のような対応であることがわかる。

XIMFeedbackの値 1.3以前の文字属性 1.4以降の文字属性
0 NS_TEXTRANGE_RAW_INPUT NS_TEXTRANGE_SELECTEDCONVERTEDTEXT
XIMReverse NS_TEXTRANGE_SELECTEDRAWTEXT NS_TEXTRANGE_SELECTEDRAWTEXT
XIMUnderline NS_TEXTRANGE_CONVERTEDTEXT NS_TEXTRANGE_CONVERTEDTEXT
XIMHighlight NS_TEXTRANGE_SELECTEDCONVERTEDTEXT NS_TEXTRANGE_SELECTEDCONVERTEDTEXT

次にmozilla/editor/libeditor/base/IMETextTxn.cppをみると、NS_TEXTRANGE_...の各値は、次のようなSELECTION_IME_...で始まる値に変換される。

変換前の値 変換後の値
NS_TEXTRANGE_RAW_INPUT SELECTION_IME_RAWINPUT
NS_TEXTRANGE_SELECTEDRAWTEXT SELECTION_IME_SELECTEDRAWTEXT
NS_TEXTRANGE_CONVERTEDTEXT SELECTION_IME_CONVERTEDTEXT
NS_TEXTRANGE_SELECTEDCONVERTEDTEXT SELECTION_IME_SELECTEDCONVERTEDTEXT

これらの値を用いて描画している部分を順に見てみよう。該当するファイルはmozilla/layout/html/base/src/nsTextFrame.cppである。

まず、テキスト属性の定義について知っておく必要がある。L1759付近をみてみよう。

// XXX letter-spacing
// XXX word-spacing
#if defined(XP_WIN) || defined(XP_OS2) || defined(XP_UNIX) || defined(XP_MAC)
#define USE_INVERT_FOR_SELECTION
#endif

// XXX we should get the following from style sheet or LookAndFeel later
#define IME_RAW_COLOR NS_RGB(198,33,66)
#define IME_CONVERTED_COLOR NS_RGB(255,198,198)

USE_INVERT_FOR_SELECTIONはUNIXでは定義される。また、IME_RAW_COLORIME_CONVERTED_COLORという配色がハードコーディングされていることも注目すべき点である。

さて、L1929付近には、SELECTION_IME_SELECTEDRAWTEXTの描画方法が記述してある★2

          case nsISelectionController::SELECTION_IME_SELECTEDRAWTEXT:{
#ifdef USE_INVERT_FOR_SELECTION
              aRenderingContext.SetColor(NS_RGB(255,255,255));
              aRenderingContext.InvertRect(aX + startOffset, aY, textWidth, rect.height);
#else
              aRenderingContext.SetColor(NS_RGB(255,255,128));
              aRenderingContext.DrawRect(aX + startOffset, aY, textWidth, rect.height);
#endif
              aTextStyle.mNormalFont->GetUnderline(offset, size);
              aRenderingContext.SetColor(IME_RAW_COLOR);
              aRenderingContext.FillRect(aX + startOffset+size, aY + baseline - offset, textWidth-2*size, size);
                                }break;

これからSELECTION_IME_SELECTEDRAWTEXTは「反転 + IME_RAW_COLORの下線付き」という属性であることがわかる。

L1946付近には、SELECTION_IME_SELECTEDCONVERTEDTEXTの描画方法が記述してある。

          case nsISelectionController::SELECTION_IME_SELECTEDCONVERTEDTEXT:{
#ifdef USE_INVERT_FOR_SELECTION
              aRenderingContext.SetColor(NS_RGB(255,255,255));
              aRenderingContext.InvertRect(aX + startOffset, aY, textWidth, rect.height);
#else
              aRenderingContext.SetColor(NS_RGB(255,255,128));
              aRenderingContext.DrawRect(aX + startOffset, aY, textWidth, rect.height);
#endif
              aTextStyle.mNormalFont->GetUnderline(offset, size);
              aRenderingContext.SetColor(IME_CONVERTED_COLOR);
              aRenderingContext.FillRect(aX + startOffset+size, aY + baseline - offset, textWidth-2*size, size);
                                }break;

これからSELECTION_IME_SELECTEDCONVERTEDTEXTは「反転 + IME_CONVERTED_COLORの下線付き」という属性であることがわかる。

L1958付近には、SELECTION_IME_CONVERTEDTEXTの描画方法が記述してある。

          case nsISelectionController::SELECTION_IME_CONVERTEDTEXT:{
              aTextStyle.mNormalFont->GetUnderline(offset, size);
              aRenderingContext.SetColor(IME_CONVERTED_COLOR);
              aRenderingContext.FillRect(aX + startOffset+size, aY + baseline - offset, textWidth-2*size, size);
                                }break;

これからSELECTION_IME_CONVERTEDTEXTは「IME_CONVERTED_COLORの下線付き」という属性であることがわかる。

L1941付近には、SELECTION_IME_RAWINPUTの描画方法が記述してある。

          case nsISelectionController::SELECTION_IME_RAWINPUT:{
              aTextStyle.mNormalFont->GetUnderline(offset, size);
              aRenderingContext.SetColor(IME_RAW_COLOR);
              aRenderingContext.FillRect(aX + startOffset+size, aY + baseline - offset, textWidth-2*size, size);
                                }break;

これからSELECTION_IME_RAWINPUTは「IME_RAW_COLORの下線付き」という属性であることがわかる。

上記をまとめると、次のような対応が得られる。

XIMFeedbackの値 1.3以前の文字属性 1.4以降の文字属性
0 IME_RAW_COLORの下線付き 反転 + IME_CONVERTED_COLORの下線付き
XIMReverse 反転 + IME_RAW_COLORの下線付き 反転 + IME_RAW_COLORの下線付き
XIMUnderline IME_CONVERTED_COLORの下線付き IME_CONVERTED_COLORの下線付き
XIMHighlight 反転 + IME_CONVERTED_COLORの下線付き 反転 + IME_CONVERTED_COLORの下線付き

この実装の興味深い点は次の通りである。

  • (1.4以降では)XIMFeedbackの値が0の場合、XIMHighlightと同じ扱いである。
  • XIMFeedbackの値をビットマスクとして扱っていない。

1.4以降でXIMFeedbackの値0の解釈が変更された理由は、単純にコミッタの質が低いからである。Bonsaiで確認して欲しい。

★2 このあたりのソースは品質がかなり低くい。担当者のレベルの低さが伝わってくる。