Re: InputString自前表示について ( No.1 ) |
- 名前:管理人 日時:2017/11/15 01:31
旧字体の一部は分かりませんが、少なくとも丸の中にカタカナのアが書かれている文字はシフトJISでは扱えない文字なので、
もし gmoさんがプロジェクトのプロパティの『構成プロパティ』→『全般』の『文字セット』の項目を
DXライブラリの標準的な設定である『マルチバイト文字セットを使用する』にされている場合は
DrawString などに渡す文字列の文字コードがシフトJISとなるので『丸の中にカタカナのアが書かれている文字』は
扱うことができません
DrawKeyInputString でシフトJISでは扱えない文字が表示できているのは、DXライブラリの内部では文字コードとして
『丸の中にカタカナのアが書かれている文字』も扱うことができる Unicode という文字コード( 厳密には UTF-16( リトルエンディアン ) )を
使用しているからです
DXライブラリを使うプログラムでも『文字セット』の項目を『Unicode文字セットを使用する』にすると、
DrawString や LoadGraph など、DXライブラリで文字列を引数とする関数が全て文字列の引数の型が char * ではなく、
Unicode の文字を扱うための型である wchar_t * になるので、『丸の中にカタカナのアが書かれている文字』も
扱うことができるようになります
ただ、『Unicode文字セットを使用する』を使用する場合は併せて以下の変更も行う必要があります
・文字列を char で扱っていた部分を全て wchar_t で扱うように変更する必要があります
( 関数の引数が char * から wchar_t * に変化しているので )
・文字列リテラルも "あいう" から L"あいう" という風に、先頭に L を記述して wchar_t の文字列リテラルとする必要があります
・テキストファイルから文字列データを読み込んでいる場合は、テキストファイルを Unicode 形式で保存しなおす必要があります
・一般的に char で日本語を含む文字列を扱う場合は『半角文字は1バイト、全角文字は2バイト』として扱いますが、
wchar_t では『半角文字も全角文字も2バイト』になります
とはいえ、徐々にシフトJISは使用されない文字コードとなってきていますので、日本以外でも通用する Unicode を
使用するプログラムに変更した方が良いと思います ( 少し手間ですが… )
|
Re: InputString自前表示について ( No.2 ) |
- 名前:yumetodo 日時:2017/11/15 18:54
>wchar_t では『半角文字も全角文字も2バイト』になります
ダウト、Windows環境ではwchar_t型はUTF16なのでサロゲートペアを考慮しないといけない(C++規格的にはwchar_tの具体的なエンコードは未規定)
|
Re: InputString自前表示について ( No.3 ) |
- 名前:通りすがり 日時:2017/11/15 20:48
yumetodoさん、
最近知った知識をひけらかしたい気持ちもわかりますが言葉遣いには気をつけましょう。
|
Re: InputString自前表示について ( No.4 ) |
- 名前:管理人 日時:2017/11/15 23:53
> yumetodoさん
> ダウト、Windows環境ではwchar_t型はUTF16なのでサロゲートペアを考慮しないといけない(C++規格的にはwchar_tの具体的なエンコードは未規定)
あまり詳しくご説明すると wchar_t の使用を躊躇されてしまうかもと思いサロゲートペアの
お話はしませんでしたが、つっこまれてしまいましたか (- -;;
> gmoさん
yumetodoさんのご指摘の通り、シフトJISコードで1バイトで表すことができない全角文字が2バイトとなっているように、
wchar_t 一つの 2バイトで表せられない文字は wchar_t を2個使って表現します( これをサロゲートペアと言います )
なので、No.1 のご返信の以下の項目は
・一般的に char で日本語を含む文字列を扱う場合は『全角文字は2バイト、半角文字は1バイト』として扱いますが、
wchar_t では『半角文字も全角文字も2バイト』になります
厳密には以下のようになります
・一般的に char で日本語を含む文字列を扱う場合は『全角文字は2バイト、半角文字は1バイト』として扱いますが、
wchar_t では『サロゲートペアの文字は4バイト、それ以外の文字は2バイト』になります
サロゲートペアの文字かどうかの判定はDXライブラリの GetCharBytes を使用することで確認することができます
// 文字列の先頭の文字のバイト数を取得する
// CharCodeFormat : 第二引数 String で渡す文字列の文字コードフォーマット( DX_CHARCODEFORMAT_SHIFTJIS 等 )
// String : 調べたい文字列のアドレス
// 戻り値 : String で渡した文字列の先頭の文字のバイト数
int GetCharBytes( int CharCodeFormat, const void *String ) ;
そして yumetodoさんのご指摘の通り wchar_t は Visual Studio では一般的には UTF-16LE という1文字2バイトの Unicode の文字コードですが、
環境によっては UTF-32LE という 1文字4バイトの Unicode の文字コードであったりと、一定ではありません( この場合 wchar_t 一つで 4バイトになります )
GetCharBytes の第一引数には wchar_t の文字コードフォーマットを渡さなければならないのですが、環境によって書き換えないと
いけないのは不便なので、wchar_t の文字コードフォーマットを返す関数を用意しています
// wchar_t型の文字コード形式を取得する
// 戻り値: DX_CHARCODEFORMAT_UTF16LE など
int Get_wchar_t_CharCodeFormat( void ) ;
なので、第一引数には Get_wchar_t_CharCodeFormat() を渡しておけば大丈夫です
というわけで、サロゲートペアかどうかを調べる if文は以下のようになります
wchar_t *UnicodeString = L"あいうえお";
// 3文字目がサロゲートペアかどうかを調べる
if( GetCharBytes( Get_wchar_t_CharCodeFormat(), &UnicodeString[ 2 ] ) / sizeof( wchar_t ) == 2 )
{
// サロゲートペアの文字
}
else
{
// サロゲートペアではない
}
GetCharBytes の戻り値を sizeof( wchar_t ) で割っているのは、GetCharBytes の戻り値が『wchar_t の数』ではなく、
『バイト数』だからです、sizeof( wchar_t ) は wchar_t 1個のバイト数なので、sizeof( wchar_t ) で戻り値を割ることで、
1文字が wchar_t 何個分で表現されているか分かります
そして、UTF-16LE も UTF-32LE も Unicode で、他にも UTF-8 や UTF-16BE などもあるのですが、全部 Unicode という文字コードです
この辺りの知識があると色々文字コード関連の疑問が解消されると思いますので、よろしければ Unicode について調べてみてください
( UTF-8 UTF-16 UTF-32 UTF-16BE UTF-16LE UTF-32LE UTF-32BE Unicode ←これらのキーワード単体や複数の組み合わせで色々な解説ページが見つかります
因みに今一番有力なのは UTF-8 です、Visual Studio 2015 以降であれば u8"あいうえお" など、先頭に u8 を書くことで
UTF-8 の文字列リテラルになります )
> 通りすがりさん
お気遣いありがとうございます
|
Re: InputString自前表示について ( No.5 ) |
- 名前:gmo 日時:2017/11/16 22:09
管理人さん、yumetodoさん、unicodeについての情報ありがとうございます。
文字コードを意識するような(込み入った)プログラムを作ることは無いだろうなという感じで特に調べることも無く、
今までやってたのですが、そのツケが来てしまったという感じです・・
それはさておき、試行錯誤中に生まれたガラクタの整理をしつつ、文字コードをunicodeでプロジェクトを作り直し、
管理人さんのアドバイス通りにコードを変えて、とりあえずユニコードの文字を処理できるようにできました。
サロゲートペアについては、それを意識して組まなければならなそうなところがあったので、管理人さんから
提示いただいた関数を駆使して修正をしてみました。
ソースコードの一部を貼らせていただきます。厚かましいですが、プログラムでunicodeを扱うのは初めてなので、
指摘や助言をいただければ幸いです。
void RectText::MakeDrawText3()
{
//文字列を矩形の幅に収まるように行替えを行った場合の
//各行の最後の文字位置を記録する → 描画時に参照
//lineEndPos[workRow].TruePos :行の最後とする文字位置
//size.cx :矩形の幅
//dispStr :InputStringから得た文字列 std::wstring型
// 改行・終端の文字幅はスペースと同じとする(矩形幅ぎりぎりの場所にカーソルを表示しにくくするため)
const wchar_t space = L' ';
int fontSpaceWidth = GetDrawStringWidthToHandle(&space, 1, fontHandle);
//コードフォーマットとwchar_tのバイトサイズ
int stringfomat = Get_wchar_t_CharCodeFormat();
int wchartsize = sizeof(wchar_t);
int charWidth = 0; //一文字分の文字幅
int workWidth = 0; //一文字分の文字幅の累積用 行が変わるタイミングでリセット
int workRow = 0; //行番号
int charSize = 0; //wchar_tの幅(個数) ※プロジェクト変更前はバイト幅
int i = 0;
do
{
workWidth += charWidth;
i += charSize;
charSize = (GetCharBytes(stringfomat, &dispStr[i])) / wchartsize;
if ((dispStr[i] == L'\0') || (dispStr[i] == L'\n'))
{
charWidth = fontSpaceWidth;
}
else
{
charWidth = GetDrawStringWidthToHandle(&dispStr[i], 1, fontHandle);
}
//矩形幅と描画幅を比較し、矩形幅より大きくなった場合、直前の文字位置を記録し次の行へ
if (workWidth + charWidth > size.cx)
{
lineEndPos[workRow].TruePos = i - 1;
workRow++;
workWidth = 0;
}
//改行の場合は改行位置を記録し次の行へ
if ((dispStr[i] == L'\n'))
{
lineEndPos[workRow].TruePos = i;
// カーソルと同一の場合、カーソルのローカル位置を設定
if(i == cur)
{
curLocal.x = workWidth;
curLocal.y = lineHeight * workRow;
curSize.cx = charWidth;
curSize.cy = lineHeight;
}
workRow++;
workWidth = 0;
charWidth = 0;
//次の処理へ
continue;
}
//終端文字の場合はその文字位置を記録
if ((dispStr[i] == L'\0'))
{
lineEndPos[workRow].TruePos = i;
}
// カーソルと同一の場合、カーソルのローカル位置を設定
if (i == cur)
{
curLocal.x = workWidth;
curLocal.y = lineHeight * workRow;
curSize.cx = charWidth;
curSize.cy = lineHeight;
}
} while (dispStr[i] != L'\0');
//全行数の確定
rowNum = workRow + 1;
}
void RectText::Draw()
{
//枠線の描画
DrawBox(rect, GetColor(SNOW), false);
//文字列の描画
int drawst = 0; //描画スタート位置
for (int i = 0; i < rowNum; ++i)
{
int drawsize = lineEndPos[i].TruePos - drawst + 1;
if (0 < drawsize)
{
DrawStringToHandle(rect.left, rect.top + (i * lineHeight), dispStr.substr(drawst, drawsize).c_str(), color, fontHandle);
}
drawst = lineEndPos[i].TruePos + 1;
}
//カーソル
RECT currect;
currect.left = rect.left + curLocal.x;
currect.top = rect.top + curLocal.y;
currect.right = currect.left + curSize.cx;
currect.bottom = currect.top + curSize.cy;
DrawBox(currect, GetColor(255, 0, 0), TRUE);
//IME入力関連の描画
conv.Draw();
}
(関数名や変数名については突っ込みなしでお願いします・・試行錯誤中に目的が変わってしまったのが多数なので・・
MakeDrawText()も当初は矩形幅を参照して改行を挿入した文字列を作成するものでした・・・)
|
Re: InputString自前表示について ( No.6 ) |
- 名前:yumetodo 日時:2017/11/18 12:50
さしあたり私の手元のDxLib.h見る限りではGetCharBytesの第一引数と第二引数がそれだと逆だと思うんですが・・・
|
Re: InputString自前表示について ( No.7 ) |
- 名前:gmo 日時:2017/11/18 17:28
yumetodoさんありがとうございます。
> さしあたり私の手元のDxLib.h見る限りではGetCharBytesの第一引数と第二引数がそれだと逆だと思うんですが・・・
引数の順番が逆なのでしょうか?一応ビルドは通り、一通りは動いているのですが・・・
|
Re: InputString自前表示について ( No.8 ) |
- 名前:管理人 日時:2017/11/19 13:48
> {
> charWidth = GetDrawStringWidthToHandle(&dispStr[i], 1, fontHandle);
> }
こちらで第二引数が 1 になっていますが、サロゲートペアの場合は 2 にする必要がありますので、
こちらは 1 ではなく直前で取得している charSize にする必要があります
{
charWidth = GetDrawStringWidthToHandle(&dispStr[i], charSize, fontHandle);
}
> //矩形幅と描画幅を比較し、矩形幅より大きくなった場合、直前の文字位置を記録し次の行へ
> if (workWidth + charWidth > size.cx)
> {
> lineEndPos[workRow].TruePos = i - 1;
こちらでは i - 1 を『一つ前の文字の位置』とされていますが、『一つ前の文字』がサロゲートペアの場合、
i - 2 にする必要がありますので、do{ ループの最初の
> i += charSize;
>
> charSize = (GetCharBytes(stringfomat, &dispStr[i])) / wchartsize;
こちらで『一つ前の文字の位置』と『一つ前の文字のサイズ』が失われてしまっていますが、こちらを
別の変数などに一時的に保存するようにして
int backupI = i;
i += charSize;
int backupCharSize = charSize;
charSize = (GetCharBytes(stringfomat, &dispStr[i])) / wchartsize;
lineEndPos[workRow].TruePos = i - 1; の代わりに
lineEndPos[workRow].TruePos = backupI;
又は、
lineEndPos[workRow].TruePos = i - backupCharSize;
と、する必要があります
|
Re: InputString自前表示について ( No.9 ) |
- 名前:gmo 日時:2017/11/19 21:28
管理人さんありがとうございます。
> こちらでは i - 1 を『一つ前の文字の位置』とされていますが、『一つ前の文字』がサロゲートペアの場合、
> i - 2 にする必要がありますので、do{ ループの最初の
位置についての考え方について理解しました。ありがとうございます。
ですが今回のソースについては「ひとつ前の文字の位置」というコメントの方がおかしかったようです・・・
非常に申し訳ないです。
記録した値から次の行の先頭位置と行の文字列のサイズを計算するので、「ひとつ前の文字」の「サイズ」も
必要になってます。
行の最後の要素の位置を記録する、というのが正しい表現でしょうか・・あまりよくわかりません。
このやり方も、行の先頭位置を記録するか、もしくは行の文字列のサイズを記録するかのどちらかに
変えるべきなのかもしれませんね・・
> こちらで第二引数が 1 になっていますが、サロゲートペアの場合は 2 にする必要がありますので、
> こちらは 1 ではなく直前で取得している charSize にする必要があります
もしかしたらバグ報告になるかもしれないので少し経緯を交えて記載させていただきます。
GetDrawStringWidthToHandleについてですが
貼り付けたコードのベースはSwordBout付属のToollib中のStringInput中のもので
(そこでは一文字ごとのcharSizeが設定されていた)
それを改造してマルチバイトで開発してたときに自分でもちょっと気になった個所です。
マルチバイトでこの関数に全角のcharSizeを設定しますと、取得する値がなぜか大きくなりました。
その時は2倍でした。
単純に、文字数で計算するように仕様変更があったのだろうなと思って実数の1を設定したところ、
普通に処理してくれたのでその後はあまり気にしませんでしたが・・
マルチバイト版では
charSize = GetCharBytes(DX_CHARCODEFORMAT_SHIFTJIS, &dispStr[i]);
if ((dispStr[i] == '\0') || (dispStr[i] == '\n'))
{
charWidth = fontSpaceWidth;
}
else
{
charWidth = GetDrawStringWidthToHandle(&dispStr[i], 1, fontHandle);
}
で処理していました。(dispStrはstd::string型)
unicodeに変えてもサロゲートペアかどうかにかかわらず実数の1で普通に取得できてる感じだったので
気にはしておりませんでしたが、先ほどの指摘からcharSizeを設定して確認したところ、こちらは大きな値とは
ならないようで、サロゲートペアも普通に処理してました。
ちなみにソースを張り付けたときはサロゲートペアの文字をどう入力したらいいかわかりませんでしたが、
今は「吉田(よしだ)」の吉の字の上が「土」のものを入力して確認してます。
サロゲートペアの一覧も見つけたのでそれのコピペでも確認してます。
また、先ほどマルチバイト版にてcharSize設定で動かしたところ、やはり大きな値が来ることがあり、
どうも全角文字の後続の文字が値に影響してるようです。全角文字単独一文字の場合は普通と思われる
値が返ってくるようです。
(※一度取り下げた内容に加筆・修正して再掲しております)
|
Re: InputString自前表示について ( No.10 ) |
- 名前:gmo 日時:2017/11/19 22:33
(一覧の最終更新日が更新されてないので空投稿させていただきます。修正使うんじゃなかった・・・)
|
Re: InputString自前表示について ( No.11 ) |
- 名前:管理人 日時:2017/11/20 01:08
|
Re: InputString自前表示について ( No.12 ) |
- 名前:gmo 日時:2017/11/20 23:53
>『ひとつ前の文字の位置』と書いたのは私です、gmoさんが組まれたプログラムのコメントには
>『ひとつ前の文字の位置』との記述はありません
失礼しました。私がソースにコメントとして書いたのは「直前の文字位置」でした。
言いたかったのは、実際記録してるのは何かというところで、ちゃんとしたコメントを当てていなかったということです。
今は「直前の文字位置」でなく、「直前を区切り位置に」と表現すべきだったかなと思ってます。
これも適した表現であるかどうか自分でも自信がないのですが・・
Draw()にあるように、区切り位置+1 で (次の)行の先頭、 区切り位置−先頭+1 で 行の(wchar_t)サイズ を計算して
一行ずつ描画してます。
なので、記録する「行の最後」について、最後の文字がサロゲートペアの場合「文字位置」で記録してしまうと
その文字の後半のwchar_t分が抜けてしまい、先頭とサイズの割り出し双方に問題が出てしまいます。
なので行の末尾にサロゲートペア文字が来たとしたら、区切り位置がサロゲートペアのおしりに来るようにすれば問題ないと
思っており、ループで処理中の文字のwchar_t(先頭)位置から-1をすればいい・・という認識なのですが・・
この説明で余計ややこしくしてしまったらスミマセン・・
>引数 StrLen の値を正しく処理( マルチバイト版では全角文字は 2 で換算 )するように修正した
>バージョンをアップしましたので、よろしければお使いください m(_ _;m
当方のマルチバイトのプログラムにてGetDrawStringWidthToHandleのみテストしました。
全角文字一文字を評価する際、「長さ」として実数1、charSize(全角のため2が入る想定)の設定でそれぞれ動かしたところ、
実数1の場合は0が、charSizeの場合は描画幅として正しいと思われる値が返ってきました。
同様にunicode版にて、サロゲートペア文字一文字を評価する際、長さとして実数1、charSize(サロゲのため2が入る想定)の設定で
それぞれ動かしたところ、双方とも同じ値が返り、描画幅としては正しいと思われる値が返ってきました。
Unicode版についての結果はDx.Lib現行バージョンと同じと思われます。
サロゲートペア文字として使用した文字は前の返信で記載した「吉田」の吉の字(士の部分が土)です。
unicodeのほうは変更なしが正しいのかどうか解らなかったため、このように記載させていただきました。
|
Re: InputString自前表示について ( No.13 ) |
- 名前:管理人 日時:2017/11/23 02:18
|
Re: InputString自前表示について ( No.14 ) |
- 名前:gmo 日時:2017/11/23 19:53
>修正したバージョンをアップしましたので、何度もお手数で申し訳ありませんが
>よろしければこちらの修正版をお試しください m(_ _;m
unicode版にて前回と同じテストを行いました。
処理する文字がサロゲートペアの場合
実数1の場合は0が、charSize(設定されたのは2)の場合は正しいと思われる値が返ってきました。
修正作業お疲れ様です。
>なるほど、理解しました
>i - 1 で正しかったのですね
自分としてはそういう認識で、表示自体も問題らしい問題は出てないです。
ですが、位置ではなく行のサイズを記録する方向での変更を検討してます・・
長引かせて申し訳ありませんが、もう一つ質問があります。聞くのが少し怖いですが・・
unicode移行のための修正で、文節情報(IMEINPUTCLAUSEDATA)を扱ってる箇所は、
dxlib.hのコメントにあるような「バイト」ではなくwchar_tの数として扱って意図通りの動きをするのですが、
こちらはwchar_tの数という認識で問題ないでしょうか。
(マルチバイト版ではコメント通り「バイト」で扱って意図通りの動きをしました。つまりその部分はunicode移行に関する
修正は施さなくても意図通りに動いたということです)
|
Re: InputString自前表示について ( No.15 ) |
- 名前:管理人 日時:2017/11/24 00:55
> unicode版にて前回と同じテストを行いました。
> 処理する文字がサロゲートペアの場合
> 実数1の場合は0が、charSize(設定されたのは2)の場合は正しいと思われる値が返ってきました。
> 修正作業お疲れ様です。
お試しいただきありがとうございます
正常に動作した様で何よりです m(_ _;m
> 長引かせて申し訳ありませんが、もう一つ質問があります。聞くのが少し怖いですが・・
> unicode移行のための修正で、文節情報(IMEINPUTCLAUSEDATA)を扱ってる箇所は、
> dxlib.hのコメントにあるような「バイト」ではなくwchar_tの数として扱って意図通りの動きをするのですが、
> こちらはwchar_tの数という認識で問題ないでしょうか。
はい、ご指摘の通り「バイト」ではなく文字数( 正確には strlen や wcslen などの戻り値と同じ
char や wchar_t などの文字を扱う変数単位での数 )となります
コメントを修正しておきます m(_ _;m
|
Re: InputString自前表示について ( No.16 ) |
- 名前:gmo(解決) 日時:2017/11/24 20:15
>はい、ご指摘の通り「バイト」ではなく文字数( 正確には strlen や wcslen などの戻り値と同じ
>char や wchar_t などの文字を扱う変数単位での数 )となります
ご回答ありがとうございます。
なんとなくですが、ライブラリをunicodeで使用するにあたっての前提というか決まり事が見えてきた気がします。
実は「文字がうまく表示できない」以外にひとつ要望があったのですが、自分の課題も増えたのでそれを消化したあとに
改めて要望させていただきます。
よって解決とさせていただきます。
管理人さん、yumetodoさんありがとうございました。
yumetodoさんのサロゲートペアについての早くからの言及と、管理人さんからの具体的な修正方法のおかげで
移行作業がスムーズにできました。
|