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クライアントはこの値をビットマスクとして解釈していない。
そのほか、XIMHighlight
とXIMReverse
が同じ文字属性というのも注意したい点である。
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_COLOR
とIME_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 このあたりのソースは品質がかなり低くい。担当者のレベルの低さが伝わってくる。