- []
- [以前のリビジョン]
文章の描画
通常の文字の扱い
タグは、コンダクタのgetNextTagメソッドで次のタグが解析され、タグハンドラを呼ばれて処理されていました。通常、文章として描画される文字はどのように処理されるでしょうか。コンダクタのonTagを書き換えて、getNextTagで解析された結果の辞書配列を、コンソール上に表示させてみました。
見てもらったらわかるように、waitタグは%["tagname" => "wait", "time" => "200"]のように表示されています。通常の文字はというと、一文字ずつchタグが呼ばれていると解釈されているようです。(実際そう解析されます。→(trac)core/utils/KAGParser.cpp#L1493)
13:09:57 first.ks : [wait time=200]
13:09:57 onTag: %["tagname" => "wait", "time" => "200"]
13:09:58 first.ks : ラベル/ページ : *start/スタート
13:09:58 first.ks : [cm]
13:09:58 onTag: %["tagname" => "cm"]
13:09:58 first.ks : こんにちは。
13:09:58 onTag: %["tagname" => "ch", "text" => "こ"]
13:09:58 onTag: %["tagname" => "ch", "text" => "こ"]
13:09:58 onTag: %["tagname" => "ch", "text" => "ん"]
13:09:58 onTag: %["tagname" => "ch", "text" => "に"]
13:09:58 onTag: %["tagname" => "ch", "text" => "ち"]
13:09:58 onTag: %["tagname" => "ch", "text" => "は"]
13:09:58 onTag: %["tagname" => "ch", "text" => "。"]
#############################################
# scenario/AfterInit.tjs
# 汚すぎて涙が出る
var orgOnTag = kag.mainConductor.onTag;
kag.mainConductor.onTag = function(obj){
var dic = new Dictionary();
(Dictionary.assign incontextof dic)(obj);
var line = "%[\"tagname\" => \"" + dic.tagname + "\"";
delete dic.tagname;
var ar = new Array();
ar.assign(dic);
var i = ar.count;
while(i >= 2){
line += ", \"" + ar[i-2] + "\" => \"" + ar[i-1] + "\"";
i -= 2;
}
line += "]";
dm("onTag: " + line);
return orgOnTag(obj);
};
chタグ
実際に文字を描画しているのは4579行目のprocessChメソッドです。マージン(余白)などを計算しながら、改行しなきゃいけないような位置にあるのかとか、いろいろやってます。気になる人は各自ソースを眺めてください。MessageLayer.tjsの1103行目です。
今回注目したいのは、actualChSpeedという値です。このactualChSpeedはデフォルトの値が30ミリ秒です。文字描画の実際のスピードを格納しています。実際のスピードとは、基本的には文字の描画スピードchSpeedと同じ値をとりますが、スキップのときだけはchSpeedの値ではなく0を値にとります。
そして、chタグ内ではいろいろやっていますが、最終的にacsを返り値として返しています。すなわち実際の文字描画のスピードであるactualChSpeedの値です。
// kag3/templete/system/MainWindow.tjs
4569: ch : function(elm)
4570: {
4571: // 文字表示
4572: var acs = actualChSpeed;
4573: if(updateBeforeCh)
4574: {
4575: if(acs) { updateBeforeCh--; return -5; } else { updateBeforeCh--; }
4576: }
4577: var text = elm.text;
4578: if(currentWithBack) current.comp.processCh(text);
4579: if(current.processCh(text))
4580: {
4581: return showPageBreakAndClear();
4582: }
4583: if(historyWriteEnabled) historyLayer.store(text);
4584: if(autoWCEnabled)
4585: {
4586: // 自動ウェイト
4587: var ind;
4588: if((ind = autoWCChars.indexOf(text)) != -1)
4589: {
4590: return int(acs * autoWCWaits[ind]);
4591: }
4592: }
4593: return acs;
4594: } incontextof this,
さて、タグハンドラが返す値は0のときは、ウェイトをかけずに次のタグを取得しにいっていました。正の値をとるときはtimerのinterval(間隔)をその値にしています。つまり、ここでは30ミリ秒にしています。
ということは、30ミリ秒後にgetNextTagを呼ぶことになります。つまり、「“こ”を描画」30ミリ秒待つ「“ん”を描画」30ミリ秒待つ……といったことをしているのです。この値が0のときは、まったく待たないスキップの状態となります。
// kag3/templete/system/Conductor.tjs
0103: // onTag を呼ぶ
0104: var step = onTag(obj);
0105: if(step === void)
0106: throw new Exception("onTag が void を返しました (" + obj.tagname + ")"
0107: "( おそらくタグハンドラの戻り値を返し忘れた )");
0108: step = int step; // step を数値に
0109: if(step == 0)
0110: {
0111: // ウェイトを掛けずに次へ
0112: timer.interval = 0;
0113: continue;
0114: }
0155: else
0156: {
0157: // 次へ
0158: if(timer.interval != step)
0159: {
0160: timer.interval = step;
0161: nextTimerTick = step + System.getTickCount();
0162: }
0163: inProcessing = false;
0164: reentered = false;
0165: return;
0166: }
「こ」が2つ
なぜだか「こ」が連続してますね。ですが、実際に描画されたのはちゃんとした「こんにちは。」です。
13:09:58 first.ks : こんにちは。 13:09:58 onTag: %["tagname" => "ch", "text" => "こ"] 13:09:58 onTag: %["tagname" => "ch", "text" => "こ"] 13:09:58 onTag: %["tagname" => "ch", "text" => "ん"] 13:09:58 onTag: %["tagname" => "ch", "text" => "に"] 13:09:58 onTag: %["tagname" => "ch", "text" => "ち"] 13:09:58 onTag: %["tagname" => "ch", "text" => "は"] 13:09:58 onTag: %["tagname" => "ch", "text" => "。"]
それは、通常文字として解析され、chタグとみなされ処理されかけた「こ」ですが、updateBeforeChが1であり、actualChSpeed(acs)も0でなかったため、4575行目で-5を返し、文字の描画(processCh)されなかったからです。updateBeforeChは文字を後回しにして、たまったイベントを解決させたい場面でカウントされています。
// kag3/templete/system/MainWindow.tjs
4569: ch : function(elm)
4570: {
4571: // 文字表示
4572: var acs = actualChSpeed;
4573: if(updateBeforeCh)
4574: {
4575: if(acs) { updateBeforeCh--; return -5; } else { updateBeforeCh--; }
4576: }
-5が返ると、後回しにするタグを保管しておくpendingsに追加されます。そして、oneshotのモードをatmAtIdle(優先度低い)にしてトリガを引きます。その後、returnでtimerCallbackメソッドを抜けると、たまっていた(kr2doc)非同期イベントが解決されます。そして、優先度の低かったoneshotのイベントが最後に解決されます。その結果、再びtimerCallbackメソッドが実行されます。
// kag3/templete/system/Conductor.tjs
115: else if(step < 0)
0116: {
0117: switch(step)
0118: {
0119: case -5: // いったんイベントを処理(現在のタグは後回し)
0120: pendings.insert(0, obj);
0121: oneshot.mode = atmAtIdle;
0122: oneshot.trigger(); // トリガ
0123: timer.interval = 0; // タイマは停止
0124: inProcessing = false;
0125: reentered = false;
0126: return;
pendingsに追加されたタグは再び処理される。2回目はupdateBeforeChも0なので、無事描画される。
// kag3/templete/system/Conductor.tjs
0070: for(;;)
0071: {
0072: if(pendings.count > 0)
0073: {
0074: // 後回しにされたタグがある場合
0075: obj = pendings[0];
0076: pendings.erase(0);
0077: }
delayタグ
文字の描画速度を調整するdelayタグについて見てみます。目的のタグハンドラを見つけるときはMainWindow.tjsを「tagname :」で検索すると探しやすいです。
delayタグはsetDelayを呼んでいます。
// kag3/templete/system/MainWindow.tjs
4825: delay : function(elm)
4826: {
4827: // 文字表示速度の指定
4828: setDelay(elm);
4829: return 0;
4830: } incontextof this,
ということで、「function setDelay」で検索してみましょう。
elm.speedが'nowait'のときはchSpeed(文字の描画スピード)を0にしています。また、'user'でないときはelm.speedを数値としてみなしてchSpeedに代入しています。ともに、ユーザの設定した速度ではないので、chUserModeを偽にしています。
elm.speedが'user'のときはchUserModeを真にしてsetUserSpeedを呼んでいます。このメソッドは未読か既読かを判断して、それぞれのユーザが設定した文字速度をchSpeedに代入しています。ユーザはメニューバーから文字の速度を設定することができます。このsetUserSpeedメソッドは、ラベルを通過するごとに呼び出されて文字速度を調整しています。
そして、スキップ中でなければ、実際の文字速度にchSpeedを反映させています。スキップ中はactualChSpeedは0のままです。
// kag3/templete/system/MainWindow.tjs
3399: function setDelay(elm)
3400: {
3401: // delay タグの処理
3402: var speed = elm.speed;
3403: if(speed == 'nowait')
3404: {
3405: chSpeed = 0;
3406: chUserMode = false;
3407: }
3408: else if(speed == 'user')
3409: {
3410: chUserMode = true;
3411: setUserSpeed();
3412: }
3413: else
3414: {
3415: chSpeed = +speed;
3416: chUserMode = false;
3417: }
3418: if(!skipMode) actualChSpeed = chSpeed;
3419: }
3428: function setUserSpeed()
3429: {
3430: // ユーザの選択した文字表示スピードを設定
3431: // この関数を読んだ時点ですでに userChSpeed には
3432: // あたらしい値が設定されているとみなす。
3433: // あるいは、ラベルごとに、その区域が既読か未読かで
3434: // 表示スピードを変える目的で呼ばれる
3435: if(chUserMode)
3436: {
3437: if(getCurrentRead())
3438: chSpeed = userCh2ndSpeed==-1?userChSpeed:userCh2ndSpeed; // 既読
3439: else
3440: chSpeed = userChSpeed; // 未読
3441: }
3442: if(!skipMode) actualChSpeed = chSpeed;
3443: }

