Develop and Download Open Source Software

Browse Subversion Repository

Contents of /trunk/src/ts/MultilineText.ts

Parent Directory Parent Directory | Revision Log Revision Log


Revision 170 - (show annotations) (download) (as text)
Mon May 4 03:43:41 2015 UTC (9 years ago) by tsugehara
File MIME type: text/texmacs
File size: 10636 byte(s)


1 ///<reference path="all.ts"/>
2 module jg {
3 /**
4 * 一行の情報
5 */
6 export class TextLineInfo {
7 /** 行の幅 */
8 width: number;
9 /** 行の高さ */
10 height: number;
11 /** 縦座標のオフセット値 */
12 offsetY: number;
13
14 /**
15 * コンストラクタ
16 * @param offsetY 縦座標のオフセット値
17 */
18 constructor(offsetY:number) {
19 this.width = 0;
20 this.height = 0;
21 this.offsetY = offsetY;
22 }
23 }
24
25 /**
26 * スクリプトを解析するクラス。
27 * このクラスはサンプルであり、#pageコマンドによる改ページのみしかサポートしていない。
28 */
29 export class MultilineScriptAnalyzer {
30 /** 現在のモード */
31 mode: number;
32 /** 対象のMultilineTextクラス */
33 owner: MultilineText;
34 /** 対象の描画コンテキスト */
35 context: CanvasRenderingContext2D;
36 /** ポジション */
37 pos: CommonOffset;
38 /** バッファ */
39 buf: string;
40
41 /**
42 * 初期化
43 * @param owner 対象のMultilineTextクラス
44 * @param context 描画コンテキスト
45 * @param pos ポジション
46 */
47 init(owner:MultilineText, context:CanvasRenderingContext2D, pos:CommonOffset) {
48 this.mode = 0;
49 this.owner = owner;
50 this.context = context;
51 this.pos = pos;
52 }
53
54 /**
55 * 次の文字を判定する
56 */
57 next(c:string):number {
58 if (this.mode) {
59 if (c == " ") {
60 this.mode = 0;
61 if (this.buf == "page")
62 return -1;
63 } else
64 this.buf += c;
65
66 return 1;
67 }
68 if (c == "#") {
69 this.mode = 1;
70 this.buf = "";
71 return 1;
72 }
73 return 0;
74 }
75 }
76
77 /**
78 * 複数行のテキストを扱うクラス
79 */
80 export class MultilineText extends E {
81 /** 元スクリプト */
82 script: string;
83 /** 裏画面バッファ */
84 buffer: HTMLCanvasElement;
85 /** クリッピング用Line */
86 clip: Line;
87 /** テキスト転送用Sprite */
88 sprite: Sprite;
89
90 /** デフォルトのスタイル */
91 defaultStyle: any;
92 /** デフォルトのフォント */
93 defaultFont: any;
94 /** デフォルトの影 */
95 defaultBlur: number;
96 /** デフォルトの影色 */
97 defaultShadowColor: any;
98 /** デフォルトの影オフセットX */
99 defaultShadowOffsetX: number;
100 /** デフォルトの影オフセットY */
101 defaultShadowOffsetY: number;
102 /** テキストの影を無効にするかどうか */
103 disableShadow: boolean;
104
105 /** 全行情報 */
106 lines: TextLineInfo[];
107 /** 現在アニメーション中のポジション */
108 animePos: CommonOffset;
109 /** 現在アニメーション中の行 */
110 animeLine: number;
111 /** テキストのアニメーションスピード。デフォルトは400 */
112 animeSpeed: number;
113
114 /** アニメーション完了時に発火されるイベント */
115 animated: Trigger;
116 /** スクリプト解析クラス */
117 scriptAnalyzer: MultilineScriptAnalyzer;
118 /** バッファの背景 */
119 bufferBg: ImageData;
120 /** 通常の行の高さ */
121 static LINE_HEIGHT_NORMAL: number = 1.2;
122 /** 特殊なブラウザにおける余白 */
123 static BROWSER_BASELINE_MARGIN: number = 0;
124
125 /**
126 * コンストラクタ
127 * @param size 表示サイズ
128 * @param offset 場所
129 */
130 constructor(size:CommonSize, offset?:CommonOffset) {
131 super();
132 this.scriptAnalyzer = new MultilineScriptAnalyzer();
133 this.width = size.width;
134 this.height = size.height;
135 if (offset)
136 this.moveTo(offset.x, offset.y);
137 else
138 this.moveTo(0, 0);
139
140 //this.disableShadow = true;
141 //this.defaultStyle = "#000";
142 this.defaultStyle = "#fff";
143 this.defaultFont = "18px sans-serif";//this.getDrawOption("font");
144 this.defaultBlur = 1;
145 this.defaultShadowColor = "rgba(0,0,0,0.8)";
146 this.defaultShadowOffsetX = 1;
147 this.defaultShadowOffsetY = 1;
148 /*
149 this.defaultBlur = 0.6;
150 this.defaultShadowColor = "#000";
151 this.defaultShadowOffsetX = 0.3;
152 this.defaultShadowOffsetY = 0.3;
153 */
154
155 //とりあえず全表示設定
156 this.clip = new Line({x:0, y:0});
157 this.clip.addLine(this.width, 0);
158 this.clip.addLine(this.width, this.height);
159 this.clip.addLine(0, this.height);
160 this.clip.addLine(0, this.height);
161 this.clip.addLine(0, this.height);
162 this.clip.closePath = true;
163 this.clip.setClip(true);
164
165 this.entities = [];
166 this.entities.push(this.clip);
167 this.animeSpeed = 400;
168
169 this.animated = new Trigger();
170 }
171
172 /**
173 * テキストをセットする
174 * @param text 設定する文字列
175 * @param offset 読み込み開始位置。省略時は最初から
176 */
177 setText(text:string, offset?:number):number {
178 //TODO: create plain script
179 var plainScript = text;
180 return this.setScript(plainScript, offset);
181 }
182
183 /**
184 * スクリプトをセットする
185 * @param script 設定するスクリプト
186 * @param offset 読み込み開始位置。省略時は最初から
187 */
188 setScript(script:string, offset?:number):number {
189 this.script = script.replace(/\r\n?/g, "\n");
190 this.updated();
191 return this.createBuffer(offset);
192 }
193
194 /**
195 * 行の高さを取得する
196 * @param c 対象の描画コンテキスト
197 */
198 getLineHeight(c:CanvasRenderingContext2D):number {
199 var font = c.font;
200 var firstPos = font.indexOf("px");
201 var lastPos = font.lastIndexOf(" ", firstPos);
202 if (lastPos < 0)
203 lastPos = 0;
204 if (firstPos < 0)
205 return 16; //バグっとる
206 var fontSize = parseInt(font.substring(lastPos, firstPos));
207 //line-heightはどうもnormal(= 1.2)固定になるらしい
208 //https://developer.mozilla.org/ja/docs/CSS/line-height (ほんとか?)
209 var line_height = Math.round(fontSize * MultilineText.LINE_HEIGHT_NORMAL);
210 return line_height;
211 }
212
213 /**
214 * バッファを生成する
215 * @param offset 読み込み開始位置。省略時は最初から
216 */
217 createBuffer(offset?:number):number {
218 if (! this.buffer)
219 this.buffer = window.createCanvas(this.width, this.height);
220 if (offset === undefined)
221 offset = 0;
222
223 var script = this.script;
224 var len = script.length;
225 var pos = {x: 0, y: 0}
226 var c = <CanvasRenderingContext2D>this.buffer.getContext("2d");
227 var s;
228 var m = MultilineText.BROWSER_BASELINE_MARGIN;
229 this.lines = [];
230
231 if (this.bufferBg)
232 c.putImageData(this.bufferBg, 0, 0);
233 else
234 c.clearRect(0, 0, this.width, this.height);
235
236 c.fillStyle = this.defaultStyle;
237 c.font = this.defaultFont;
238 c.textBaseline = "top";
239 if (! this.disableShadow) {
240 c.shadowBlur = this.defaultBlur;
241 c.shadowColor = this.defaultShadowColor;
242 c.shadowOffsetX = this.defaultShadowOffsetX;
243 c.shadowOffsetY = this.defaultShadowOffsetY;
244 }
245
246 var lineHeight = this.getLineHeight(c);
247 var lineInfo = new TextLineInfo(0);
248 lineInfo.height = lineHeight;
249 this.lines.push(lineInfo);
250
251 var _newLine = ():boolean => {
252 pos.x = 0;
253 pos.y += lineInfo.height; //lineHeight
254 if ((pos.y+lineInfo.height) > this.height)
255 return false;
256 lineInfo = new TextLineInfo(pos.y);
257 lineInfo.height = lineHeight;
258 this.lines.push(lineInfo);
259 return true;
260 }
261
262 this.scriptAnalyzer.init(this, c, pos);
263 while (offset < len) {
264 s = script.substr(offset, 1);
265
266 var script_ret = this.scriptAnalyzer.next(s);
267 if (script_ret) {
268 lineHeight = lineInfo.height; //スクリプト側でフォントサイズ変更した場合などの処置
269 if (script_ret < 0) {
270 offset -= script_ret;
271 break;
272 }
273 offset += script_ret;
274 continue;
275 }
276 if (s == "\n") {
277 offset++;
278 if (! _newLine())
279 break;
280 continue;
281 }
282
283 var metric = c.measureText(s);
284 if ((pos.x+metric.width) > this.width) {
285 if (! _newLine())
286 break;
287 }
288 //マニュアルシャドウの例。しかし全然綺麗に出ない。
289 //c.fillStyle = "#fff";
290 //c.fillText(s, pos.x+1, pos.y+m+1);
291 //c.fillStyle = this.defaultStyle;
292 c.fillText(s, pos.x, pos.y+m);
293 pos.x += metric.width;
294 lineInfo.width += metric.width;
295
296 offset++;
297 }
298
299 this.sprite = new Sprite(this.buffer);
300 this.sprite.moveTo(0, 0);
301 if (this.entities.length == 1)
302 this.entities.push(this.sprite);
303 else
304 this.entities[1] = this.sprite;
305
306 return offset == len ? -1 : offset;
307 }
308
309 /**
310 * バッファの再構築を行う
311 */
312 refresh() {
313 delete this.buffer;
314 this.createBuffer();
315 }
316
317 /**
318 * テキストのアニメーション表示を開始する
319 * @param animeSpeed アニメーションのスピード。省略時は前回の設定から変更しない
320 */
321 startAnimation(animeSpeed?:number) {
322 this.start();
323 this.animeLine = 0;
324 this.animePos = {x: 0, y: this.lines[this.animeLine].height}
325 if (animeSpeed !== undefined)
326 this.animeSpeed = animeSpeed;
327 this.hideAll();
328 this.clip.p[4].y = this.animePos.y;
329 this.clip.p[5].y = this.animePos.y;
330 }
331
332 /**
333 * Game.updateイベントに対するコールバック
334 * @param t 経過時間
335 */
336 update(t:number) {
337 this.animePos.x += this.animeSpeed / 1000 * t;
338 if (this.animePos.x >= this.lines[this.animeLine].width) {
339 this.animePos.x = 0;
340 this.animePos.y += this.lines[this.animeLine].height;
341 this.animeLine++;
342
343 if (this.animeLine < this.lines.length) {
344 this.clip.p[2].y = this.lines[this.animeLine].offsetY;
345 this.clip.p[3].y = this.clip.p[2].y;
346 this.clip.p[4].y = this.animePos.y;
347 this.clip.p[5].y = this.animePos.y;
348 }
349 }
350
351 if (this.animeLine >= this.lines.length) {
352 this.showAll();
353 } else {
354 this.clip.p[3].x = this.animePos.x;
355 this.clip.p[4].x = this.clip.p[3].x;
356 }
357
358 this.updated();
359 }
360
361 /**
362 * テキストをすべて非表示にする
363 */
364 hideAll() {
365 this.clip.p[0] = {x:0, y:0};
366 this.clip.p[1] = {x:this.width, y:0};
367 this.clip.p[2] = {x:this.width, y:0};
368 this.clip.p[3] = {x:0, y:0};
369 this.clip.p[4] = {x:0, y:0};
370 this.clip.p[5] = {x:0, y:0};
371 }
372
373 /**
374 * テキストをすべて表示する
375 */
376 showAll() {
377 this.clip.p[0] = {x:0, y:0};
378 this.clip.p[1] = {x: this.width, y: 0};
379 this.clip.p[2] = {x: this.width, y: this.height};
380 this.clip.p[3] = {x: 0, y: this.height};
381 this.clip.p[4] = {x: 0, y: this.height};
382 this.clip.p[5] = {x: 0, y: this.height};
383 this.stop();
384 this.animated.fire();
385 }
386 }
387 }

Back to OSDN">Back to OSDN
ViewVC Help
Powered by ViewVC 1.1.26