文章の描画

通常の文字の扱い

タグは、コンダクタの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:   }
 
inside/message.txt · 最終更新: 2009/03/09 16:03 by 115.124.160.242