1 /* -*- C++ -*-
2 *
3 * ONScripter_text.cpp - Text parser of ONScripter
4 *
5 * Copyright (c) 2001-2020 Ogapee. All rights reserved.
6 *
7 * ogapee@aqua.dti2.ne.jp
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
22 */
23
24 #include "ONScripter.h"
25
26 extern unsigned short convSJIS2UTF16( unsigned short in );
27
28 #define IS_ROTATION_REQUIRED(x) \
29 (!IS_TWO_BYTE(*(x)) || \
30 (*(x) == (char)0x81 && *((x)+1) == (char)0x50) || \
31 (*(x) == (char)0x81 && *((x)+1) == (char)0x51) || \
32 (*(x) == (char)0x81 && *((x)+1) >= 0x5b && *((x)+1) <= 0x5d) || \
33 (*(x) == (char)0x81 && *((x)+1) >= 0x60 && *((x)+1) <= 0x64) || \
34 (*(x) == (char)0x81 && *((x)+1) >= 0x69 && *((x)+1) <= 0x7a) || \
35 (*(x) == (char)0x81 && *((x)+1) == (char)0x80) )
36
37 #define IS_TRANSLATION_REQUIRED(x) \
38 ( *(x) == (char)0x81 && *((x)+1) >= 0x41 && *((x)+1) <= 0x44 )
39
shiftHalfPixelX(SDL_Surface * surface)40 void ONScripter::shiftHalfPixelX(SDL_Surface *surface)
41 {
42 SDL_LockSurface( surface );
43 unsigned char *buf = (unsigned char*)surface->pixels;
44 for (int i=surface->h ; i!=0 ; i--){
45 unsigned char c = buf[0];
46 for (int j=1 ; j<surface->w ; j++){
47 buf[j-1] = (buf[j]+c)>>1;
48 c = buf[j];
49 }
50 buf[surface->w-1] = c>>1;
51 buf += surface->pitch;
52 }
53 SDL_UnlockSurface( surface );
54 }
55
shiftHalfPixelY(SDL_Surface * surface)56 void ONScripter::shiftHalfPixelY(SDL_Surface *surface)
57 {
58 SDL_LockSurface( surface );
59 for (int j=surface->w-1 ; j>=0 ; j--){
60 unsigned char *buf = (unsigned char*)surface->pixels + j;
61 unsigned char c = buf[0];
62 for (int i=1 ; i<surface->h ; i++){
63 buf += surface->pitch;
64 *(buf-surface->pitch) = (*buf+c)>>1;
65 c = *buf;
66 }
67 *buf = c>>1;
68 }
69 SDL_UnlockSurface( surface );
70 }
71
drawGlyph(SDL_Surface * dst_surface,FontInfo * info,SDL_Color & color,char * text,int xy[2],AnimationInfo * cache_info,SDL_Rect * clip,SDL_Rect & dst_rect)72 int ONScripter::drawGlyph( SDL_Surface *dst_surface, FontInfo *info, SDL_Color &color, char* text, int xy[2], AnimationInfo *cache_info, SDL_Rect *clip, SDL_Rect &dst_rect )
73 {
74 unsigned short unicode = script_h.enc.getUTF16(text);
75
76 int minx, maxx, miny, maxy, advanced;
77 #if 0
78 if (TTF_GetFontStyle( (TTF_Font*)info->ttf_font[0] ) !=
79 (info->is_bold?TTF_STYLE_BOLD:TTF_STYLE_NORMAL) )
80 TTF_SetFontStyle( (TTF_Font*)info->ttf_font[0], (info->is_bold?TTF_STYLE_BOLD:TTF_STYLE_NORMAL));
81 #endif
82 TTF_GlyphMetrics( (TTF_Font*)info->ttf_font[0], unicode,
83 &minx, &maxx, &miny, &maxy, &advanced );
84 //printf("min %d %d %d %d %d %d\n", minx, maxx, miny, maxy, advanced,TTF_FontAscent((TTF_Font*)info->ttf_font[0]) );
85
86 static SDL_Color fcol={0xff, 0xff, 0xff}, bcol={0, 0, 0};
87 SDL_Surface *tmp_surface = TTF_RenderGlyph_Shaded((TTF_Font*)info->ttf_font[0], unicode, fcol, bcol);
88
89 SDL_Color scolor = {0, 0, 0};
90 SDL_Surface *tmp_surface_s = tmp_surface;
91 if (info->is_shadow && render_font_outline){
92 unsigned char max_color = color.r;
93 if (max_color < color.g) max_color = color.g;
94 if (max_color < color.b) max_color = color.b;
95 if (max_color < 0x80) scolor.r = 0xff;
96 else scolor.r = 0;
97 scolor.g = scolor.b = scolor.r;
98
99 tmp_surface_s = TTF_RenderGlyph_Shaded((TTF_Font*)info->ttf_font[1], unicode, fcol, bcol);
100 if (tmp_surface && tmp_surface_s){
101 if ((tmp_surface_s->w-tmp_surface->w) & 1) shiftHalfPixelX(tmp_surface_s);
102 if ((tmp_surface_s->h-tmp_surface->h) & 1) shiftHalfPixelY(tmp_surface_s);
103 }
104 }
105
106 bool rotate_flag = false;
107 if ( info->getTateyokoMode() == FontInfo::TATE_MODE && IS_ROTATION_REQUIRED(text) ) rotate_flag = true;
108
109 dst_rect.x = xy[0] + minx;
110 dst_rect.y = xy[1] + TTF_FontAscent((TTF_Font*)info->ttf_font[0]) - maxy;
111 if (script_h.enc.getEncoding() == Encoding::CODE_CP932)
112 dst_rect.y -= (TTF_FontHeight((TTF_Font*)info->ttf_font[0]) - info->font_size_xy[1]*screen_ratio1/screen_ratio2)/2;
113
114 if ( rotate_flag ) dst_rect.x += miny - minx;
115
116 if ( info->getTateyokoMode() == FontInfo::TATE_MODE && IS_TRANSLATION_REQUIRED(text) ){
117 dst_rect.x += info->font_size_xy[0]/2;
118 dst_rect.y -= info->font_size_xy[0]/2;
119 }
120
121 if (info->is_shadow && tmp_surface_s){
122 SDL_Rect dst_rect_s = dst_rect;
123 if (render_font_outline){
124 dst_rect_s.x -= (tmp_surface_s->w - tmp_surface->w)/2;
125 dst_rect_s.y -= (tmp_surface_s->h - tmp_surface->h)/2;
126 }
127 else{
128 dst_rect_s.x += shade_distance[0];
129 dst_rect_s.y += shade_distance[1];
130 }
131
132 if (rotate_flag){
133 dst_rect_s.w = tmp_surface_s->h;
134 dst_rect_s.h = tmp_surface_s->w;
135 }
136 else{
137 dst_rect_s.w = tmp_surface_s->w;
138 dst_rect_s.h = tmp_surface_s->h;
139 }
140
141 if (cache_info)
142 cache_info->blendText( tmp_surface_s, dst_rect_s.x, dst_rect_s.y, scolor, clip, rotate_flag );
143
144 if (dst_surface)
145 alphaBlendText( dst_surface, dst_rect_s, tmp_surface_s, scolor, clip, rotate_flag );
146 }
147
148 if ( tmp_surface ){
149 if (rotate_flag){
150 dst_rect.w = tmp_surface->h;
151 dst_rect.h = tmp_surface->w;
152 }
153 else{
154 dst_rect.w = tmp_surface->w;
155 dst_rect.h = tmp_surface->h;
156 }
157
158 if (cache_info)
159 cache_info->blendText( tmp_surface, dst_rect.x, dst_rect.y, color, clip, rotate_flag );
160
161 if (dst_surface)
162 alphaBlendText( dst_surface, dst_rect, tmp_surface, color, clip, rotate_flag );
163 }
164
165 if (tmp_surface_s && tmp_surface_s != tmp_surface)
166 SDL_FreeSurface(tmp_surface_s);
167 if (tmp_surface)
168 SDL_FreeSurface(tmp_surface);
169
170 return advanced;
171 }
172
openFont(FontInfo * fi)173 void ONScripter::openFont(FontInfo *fi)
174 {
175 if (fi->ttf_font[0] == NULL){
176 if (fi->openFont(font_file, screen_ratio1, screen_ratio2) == NULL){
177 fprintf( stderr, "can't open font file: %s\n", font_file );
178 quit();
179 exit(-1);
180 }
181 }
182 #if defined(PSP)
183 else
184 fi->openFont(font_file, screen_ratio1, screen_ratio2);
185 #endif
186 }
187
drawChar(char * text,FontInfo * info,bool flush_flag,bool lookback_flag,SDL_Surface * surface,AnimationInfo * cache_info,SDL_Rect * clip)188 void ONScripter::drawChar( char* text, FontInfo *info, bool flush_flag, bool lookback_flag, SDL_Surface *surface, AnimationInfo *cache_info, SDL_Rect *clip )
189 {
190 //printf("draw %x-%x[%s] %d, %d\n", text[0], text[1], text, info->xy[0], info->xy[1] );
191 openFont(info);
192
193 if (info->isEndOfLine()){
194 info->newLine();
195 for (int i=0 ; i<indent_offset ; i++){
196 if (lookback_flag){
197 if (script_h.enc.getEncoding() == Encoding::CODE_CP932){
198 current_page->add(0x81);
199 current_page->add(0x40);
200 }
201 else{
202 current_page->add(0xe3);
203 current_page->add(0x80);
204 current_page->add(0x80);
205 }
206 }
207 info->advanceCharInHankaku(2);
208 }
209 }
210
211 info->old_xy[0] = info->x(false);
212 info->old_xy[1] = info->y(false);
213
214 char text2[4] = {};
215 int n = script_h.enc.getBytes(text[0]);
216 for (int i=0; i<n; i++)
217 text2[i] = text[i];
218
219 for (int i=0 ; i<2 ; i++){
220 int xy[2];
221 xy[0] = info->x() * screen_ratio1 / screen_ratio2;
222 xy[1] = info->y() * screen_ratio1 / screen_ratio2;
223
224 SDL_Color color = {info->color[0], info->color[1], info->color[2]};
225 SDL_Rect dst_rect;
226 float adv = drawGlyph(surface, info, color, text2, xy, cache_info, clip, dst_rect);
227 if (n == 1) adv -= 0.5; // 0.5 is for adjusting the increse by FT_CEIL
228
229 if ( surface == accumulation_surface &&
230 !flush_flag &&
231 (!clip || AnimationInfo::doClipping( &dst_rect, clip ) == 0) ){
232 dirty_rect.add( dst_rect );
233 }
234 else if ( flush_flag ){
235 if (info->is_shadow){
236 if (render_font_outline)
237 info->addShadeArea(dst_rect, -1, -1, 2, 2);
238 else
239 info->addShadeArea(dst_rect, 0, 0, shade_distance[0], shade_distance[1]);
240 }
241 flushDirect( dst_rect, REFRESH_NONE_MODE );
242 }
243
244 if (n >= 2){
245 if (script_h.enc.getEncoding() == Encoding::CODE_UTF8){
246 // handle a proportional font
247 adv += info->pitch_xy[0] - info->font_size_xy[0];
248 info->advanceCharInHankaku(adv*2.0/info->pitch_xy[0]);
249 }
250 else
251 info->advanceCharInHankaku(2);
252 break;
253 }
254 if (script_h.enc.getEncoding() == Encoding::CODE_UTF8){
255 // handle a proportional font
256 adv += info->pitch_xy[0] - info->font_size_xy[0];
257 info->advanceCharInHankaku(adv*2.0/info->pitch_xy[0]);
258 }
259 else
260 info->advanceCharInHankaku(1);
261 text2[0] = text[1];
262 if (text2[0] == 0) break;
263 }
264
265 if (lookback_flag){
266 for (int i=0; i<n; i++)
267 current_page->add(text[i]);
268 }
269 }
270
drawString(const char * str,uchar3 color,FontInfo * info,bool flush_flag,SDL_Surface * surface,SDL_Rect * rect,AnimationInfo * cache_info,bool pack_hankaku)271 void ONScripter::drawString(const char *str, uchar3 color, FontInfo *info, bool flush_flag, SDL_Surface *surface, SDL_Rect *rect, AnimationInfo *cache_info, bool pack_hankaku)
272 {
273 int i;
274
275 int start_xy[2];
276 start_xy[0] = info->xy[0];
277 start_xy[1] = info->xy[1];
278
279 /* ---------------------------------------- */
280 /* Draw selected characters */
281 uchar3 org_color;
282 for ( i=0 ; i<3 ; i++ ) org_color[i] = info->color[i];
283 for ( i=0 ; i<3 ; i++ ) info->color[i] = color[i];
284
285 bool skip_whitespace_flag = true;
286 if (script_h.enc.getEncoding() == Encoding::CODE_UTF8)
287 skip_whitespace_flag = false;
288 char text[4] = {};
289 while(*str){
290 while (*str == ' ' && skip_whitespace_flag) str++;
291
292 #ifdef ENABLE_1BYTE_CHAR
293 if ( *str == '`' ){
294 str++;
295 skip_whitespace_flag = false;
296 continue;
297 }
298 #endif
299 if (script_h.enc.getEncoding() == Encoding::CODE_UTF8 &&
300 *str == script_h.enc.getTextMarker()){
301 str++;
302 continue;
303 }
304
305 #ifndef FORCE_1BYTE_CHAR
306 if (cache_info && !cache_info->is_tight_region){
307 if (*str == '('){
308 startRuby(str+1, *info);
309 str++;
310 continue;
311 }
312 else if (*str == '/' && ruby_struct.stage == RubyStruct::BODY ){
313 info->addLineOffset(ruby_struct.margin);
314 str = ruby_struct.ruby_end;
315 if (*ruby_struct.ruby_end == ')'){
316 endRuby(false, false, NULL, cache_info);
317 str++;
318 }
319 continue;
320 }
321 else if (*str == ')' && ruby_struct.stage == RubyStruct::BODY ){
322 ruby_struct.stage = RubyStruct::NONE;
323 str++;
324 continue;
325 }
326 else if (*str == '<'){
327 str++;
328 int no = 0;
329 while(*str>='0' && *str<='9')
330 no=no*10+(*str++)-'0';
331 in_textbtn_flag = true;
332 continue;
333 }
334 else if (*str == '>' && in_textbtn_flag){
335 str++;
336 in_textbtn_flag = false;
337 continue;
338 }
339 }
340 #endif
341 int n = script_h.enc.getBytes(*str);
342 if (n >= 2){
343 if (checkLineBreak(str, info)){
344 info->newLine();
345 for (int i=0 ; i<indent_offset ; i++)
346 info->advanceCharInHankaku(2);
347 }
348
349 for (int i=0; i<n; i++)
350 text[i] = *str++;
351 drawChar(text, info, false, false, surface, cache_info);
352 }
353 else if (*str == 0x0a || (*str == '\\' && info->is_newline_accepted)){
354 info->newLine();
355 str++;
356 }
357 else if (script_h.enc.getEncoding() == Encoding::CODE_UTF8 &&
358 *str == '~'){
359 while(1){
360 str++;
361 if (*str == 0x0a || *str == 0) break;
362 if (*str == '~'){
363 str++;
364 break;
365 }
366 if (*str == 'i'){
367 openFont(info);
368 info->toggleStyle(TTF_STYLE_ITALIC);
369 }
370 else if (*str == 'b'){
371 openFont(info);
372 info->toggleStyle(TTF_STYLE_BOLD);
373 }
374 }
375 continue;
376 }
377 else if (*str){
378 if (checkLigatureLineBreak(str, info))
379 info->newLine();
380 text[0] = *str++;
381 if (*str && *str != 0x0a && pack_hankaku &&
382 script_h.enc.getEncoding() == Encoding::CODE_CP932)
383 text[1] = *str++;
384 else
385 text[1] = 0;
386 drawChar( text, info, false, false, surface, cache_info );
387 }
388 }
389 for ( i=0 ; i<3 ; i++ ) info->color[i] = org_color[i];
390
391 /* ---------------------------------------- */
392 /* Calculate the area of selection */
393 SDL_Rect clipped_rect = info->calcUpdatedArea(start_xy, screen_ratio1, screen_ratio2);
394
395 SDL_Rect scaled_clipped_rect;
396 scaled_clipped_rect.x = clipped_rect.x * screen_ratio1 / screen_ratio2;
397 scaled_clipped_rect.y = clipped_rect.y * screen_ratio1 / screen_ratio2;
398 scaled_clipped_rect.w = clipped_rect.w * screen_ratio1 / screen_ratio2;
399 scaled_clipped_rect.h = clipped_rect.h * screen_ratio1 / screen_ratio2;
400
401 if (info->is_shadow){
402 if (render_font_outline)
403 info->addShadeArea(scaled_clipped_rect, -1, -1, 2, 2);
404 else
405 info->addShadeArea(scaled_clipped_rect, 0, 0, shade_distance[0], shade_distance[1]);
406 }
407
408 if (flush_flag)
409 flush(refresh_shadow_text_mode, &scaled_clipped_rect);
410
411 if (rect) *rect = clipped_rect;
412 }
413
restoreTextBuffer(SDL_Surface * surface)414 void ONScripter::restoreTextBuffer(SDL_Surface *surface)
415 {
416 text_info.fill( 0, 0, 0, 0 );
417
418 char out_text[4] = {};
419 FontInfo f_info = sentence_font;
420 f_info.clear();
421 for (int i=0 ; i<current_page->text_count ; i++){
422 if (current_page->text[i] == 0x0a){
423 f_info.newLine();
424 }
425 else{
426 out_text[0] = current_page->text[i];
427 #ifndef FORCE_1BYTE_CHAR
428 if (out_text[0] == '('){
429 startRuby(current_page->text + i + 1, f_info);
430 continue;
431 }
432 else if (out_text[0] == '/' && ruby_struct.stage == RubyStruct::BODY ){
433 f_info.addLineOffset(ruby_struct.margin);
434 i = ruby_struct.ruby_end - current_page->text - 1;
435 if (*ruby_struct.ruby_end == ')'){
436 endRuby(false, false, surface, &text_info);
437 i++;
438 }
439 continue;
440 }
441 else if (out_text[0] == ')' && ruby_struct.stage == RubyStruct::BODY ){
442 ruby_struct.stage = RubyStruct::NONE;
443 continue;
444 }
445 else if (out_text[0] == '<'){
446 int no = 0;
447 while(current_page->text[i+1]>='0' && current_page->text[i+1]<='9')
448 no=no*10+current_page->text[(i++)+1]-'0';
449 in_textbtn_flag = true;
450 continue;
451 }
452 else if (out_text[0] == '>' && in_textbtn_flag){
453 in_textbtn_flag = false;
454 continue;
455 }
456 #endif
457
458 int n = script_h.enc.getBytes(out_text[0]);
459 if (n >= 2){
460 for (int j=1 ; j<n; j++)
461 out_text[j] = current_page->text[i+j];
462
463 if (checkLineBreak(current_page->text+i, &f_info))
464 f_info.newLine();
465 i += n-1;
466 }
467 else if (script_h.enc.getEncoding() == Encoding::CODE_UTF8 &&
468 out_text[0] == '~'){
469 while(1){
470 char ch = current_page->text[++i];
471 if (ch == 0x0a || ch == 0) break;
472 if (ch == '~'){
473 i++;
474 break;
475 }
476 if (ch == 'i'){
477 openFont(&f_info);
478 f_info.toggleStyle(TTF_STYLE_ITALIC);
479 }
480 else if (ch == 'b'){
481 openFont(&f_info);
482 f_info.toggleStyle(TTF_STYLE_BOLD);
483 }
484 }
485 continue;
486 }
487 else{
488 if (checkLigatureLineBreak(current_page->text+i, &f_info))
489 f_info.newLine();
490
491 out_text[1] = 0;
492
493 if (i+1 != current_page->text_count &&
494 current_page->text[i+1] != 0x0a &&
495 script_h.enc.getEncoding() == Encoding::CODE_CP932){
496 out_text[1] = current_page->text[i+1];
497 i++;
498 }
499 }
500 drawChar(out_text, &f_info, false, false, surface, &text_info);
501 }
502 }
503 }
504
enterTextDisplayMode(bool text_flag)505 void ONScripter::enterTextDisplayMode(bool text_flag)
506 {
507 if (line_enter_status <= 1 && (!pretextgosub_label || saveon_flag) && internal_saveon_flag && text_flag){
508 storeSaveFile();
509 internal_saveon_flag = false;
510 }
511
512 if (!(display_mode & DISPLAY_MODE_TEXT)){
513 dirty_rect.clear();
514 dirty_rect.add( sentence_font_info.pos );
515 display_mode = DISPLAY_MODE_TEXT;
516
517 if (setEffect(&window_effect)) return;
518 while(doEffect(&window_effect, false));
519
520 text_on_flag = true;
521 }
522 }
523
leaveTextDisplayMode(bool force_leave_flag)524 void ONScripter::leaveTextDisplayMode(bool force_leave_flag)
525 {
526 if (display_mode & DISPLAY_MODE_TEXT &&
527 (force_leave_flag || erase_text_window_mode != 0)){
528
529 dirty_rect.add(sentence_font_info.pos);
530 display_mode = DISPLAY_MODE_NORMAL;
531
532 if (setEffect(&window_effect)) return;
533 while(doEffect(&window_effect, false));
534 }
535 }
536
doClickEnd()537 bool ONScripter::doClickEnd()
538 {
539 bool ret = false;
540
541 refresh_shadow_text_mode |= REFRESH_CURSOR_MODE;
542
543 if ( automode_flag ){
544 event_mode = WAIT_TEXT_MODE | WAIT_INPUT_MODE | WAIT_VOICE_MODE | WAIT_TIMER_MODE;
545 if ( automode_time < 0 )
546 ret = waitEvent( -automode_time * num_chars_in_sentence );
547 else
548 ret = waitEvent( automode_time );
549 }
550 else if ( autoclick_time > 0 ){
551 event_mode = WAIT_TIMER_MODE;
552 ret = waitEvent( autoclick_time );
553 }
554 else{
555 event_mode = WAIT_TEXT_MODE | WAIT_INPUT_MODE | WAIT_TIMER_MODE;
556 ret = waitEvent(-1);
557 }
558
559 num_chars_in_sentence = 0;
560
561 refresh_shadow_text_mode &= ~REFRESH_CURSOR_MODE;
562 stopAnimation( clickstr_state );
563
564 return ret;
565 }
566
clickWait(char * out_text)567 bool ONScripter::clickWait( char *out_text )
568 {
569 flush( REFRESH_NONE_MODE );
570 skip_mode &= ~SKIP_TO_EOL;
571
572 string_buffer_offset += script_h.checkClickstr(script_h.getStringBuffer() + string_buffer_offset);
573
574 if ( (skip_mode & (SKIP_NORMAL | SKIP_TO_EOP) || ctrl_pressed_status) && !textgosub_label ){
575 clickstr_state = CLICK_NONE;
576 if ( out_text ){
577 drawChar( out_text, &sentence_font, false, true, accumulation_surface, &text_info );
578 }
579 else{ // called on '@'
580 flush(refreshMode());
581 }
582 num_chars_in_sentence = 0;
583
584 event_mode = IDLE_EVENT_MODE;
585 if ( waitEvent(0) ) return false;
586 }
587 else{
588 if ( out_text ){
589 drawChar( out_text, &sentence_font, true, true, accumulation_surface, &text_info );
590 num_chars_in_sentence++;
591 }
592
593 while( (!(script_h.getEndStatus() & ScriptHandler::END_1BYTE_CHAR) &&
594 script_h.getStringBuffer()[ string_buffer_offset ] == ' ') ||
595 script_h.getStringBuffer()[ string_buffer_offset ] == '\t' ) string_buffer_offset ++;
596
597 if (textgosub_label){
598 saveon_flag = false;
599
600 textgosub_clickstr_state = CLICK_WAIT;
601 if (script_h.getStringBuffer()[string_buffer_offset] == 0x0)
602 textgosub_clickstr_state |= CLICK_EOL;
603 gosubReal(textgosub_label, script_h.getWait(), true);
604
605 event_mode = IDLE_EVENT_MODE;
606 waitEvent(0);
607
608 return false;
609 }
610
611 // if this is the end of the line, pretext becomes enabled
612 if (script_h.getStringBuffer()[string_buffer_offset] == 0x0)
613 line_enter_status = 0;
614
615 clickstr_state = CLICK_WAIT;
616 if (doClickEnd()) return false;
617
618 clickstr_state = CLICK_NONE;
619
620 if (pagetag_flag) processEOT();
621 page_enter_status = 0;
622 }
623
624 return true;
625 }
626
clickNewPage(char * out_text)627 bool ONScripter::clickNewPage( char *out_text )
628 {
629 if ( out_text ){
630 drawChar( out_text, &sentence_font, true, true, accumulation_surface, &text_info );
631 num_chars_in_sentence++;
632 }
633
634 flush( REFRESH_NONE_MODE );
635 skip_mode &= ~SKIP_TO_EOL;
636
637 string_buffer_offset += script_h.checkClickstr(script_h.getStringBuffer() + string_buffer_offset);
638
639 if ( (skip_mode & SKIP_NORMAL || ctrl_pressed_status) && !textgosub_label ){
640 num_chars_in_sentence = 0;
641 clickstr_state = CLICK_NEWPAGE;
642
643 event_mode = IDLE_EVENT_MODE;
644 if (waitEvent(0)) return false;
645 }
646 else{
647 while( (!(script_h.getEndStatus() & ScriptHandler::END_1BYTE_CHAR) &&
648 script_h.getStringBuffer()[ string_buffer_offset ] == ' ') ||
649 script_h.getStringBuffer()[ string_buffer_offset ] == '\t' ) string_buffer_offset ++;
650
651 if (textgosub_label){
652 saveon_flag = false;
653
654 textgosub_clickstr_state = CLICK_NEWPAGE;
655 gosubReal(textgosub_label, script_h.getWait(), true);
656
657 event_mode = IDLE_EVENT_MODE;
658 waitEvent(0);
659
660 return false;
661 }
662
663 // if this is the end of the line, pretext becomes enabled
664 if (script_h.getStringBuffer()[string_buffer_offset] == 0x0)
665 line_enter_status = 0;
666
667 clickstr_state = CLICK_NEWPAGE;
668 if (doClickEnd()) return false;
669 }
670
671 newPage();
672 clickstr_state = CLICK_NONE;
673
674 return true;
675 }
676
startRuby(const char * buf,FontInfo & info)677 void ONScripter::startRuby(const char *buf, FontInfo &info)
678 {
679 ruby_struct.stage = RubyStruct::BODY;
680 ruby_font = info;
681 ruby_font.ttf_font[0] = NULL;
682 ruby_font.ttf_font[1] = NULL;
683 if ( ruby_struct.font_size_xy[0] != -1 )
684 ruby_font.font_size_xy[0] = ruby_struct.font_size_xy[0];
685 else
686 ruby_font.font_size_xy[0] = info.font_size_xy[0]/2;
687 if ( ruby_struct.font_size_xy[1] != -1 )
688 ruby_font.font_size_xy[1] = ruby_struct.font_size_xy[1];
689 else
690 ruby_font.font_size_xy[1] = info.font_size_xy[1]/2;
691
692 ruby_struct.body_count = 0;
693 ruby_struct.ruby_count = 0;
694
695 while(1){
696 if ( *buf == '/' ){
697 ruby_struct.stage = RubyStruct::RUBY;
698 ruby_struct.ruby_start = buf+1;
699 buf++;
700 }
701 else if ( *buf == ')' || *buf == '\0' ){
702 break;
703 }
704 else{
705 int n = script_h.enc.getBytes(*buf);
706 if (ruby_struct.stage == RubyStruct::BODY)
707 ruby_struct.body_count += (n == 1)?1:2;
708 else if (ruby_struct.stage == RubyStruct::RUBY)
709 ruby_struct.ruby_count += (n == 1)?1:2;
710 buf += n;
711 }
712 }
713 ruby_struct.ruby_end = buf;
714 ruby_struct.stage = RubyStruct::BODY;
715 ruby_struct.margin = ruby_font.initRuby(info, ruby_struct.body_count/2, ruby_struct.ruby_count/2);
716 }
717
endRuby(bool flush_flag,bool lookback_flag,SDL_Surface * surface,AnimationInfo * cache_info)718 void ONScripter::endRuby(bool flush_flag, bool lookback_flag, SDL_Surface *surface, AnimationInfo *cache_info)
719 {
720 char out_text[4]= {};
721 if (sentence_font.rubyon_flag){
722 ruby_font.clear();
723 const char *buf = ruby_struct.ruby_start;
724 while(buf < ruby_struct.ruby_end){
725 int n = 2;
726 if (script_h.enc.getEncoding() == Encoding::CODE_UTF8)
727 n = script_h.enc.getBytes(buf[0]);
728 for (int i=0; i<n; i++)
729 out_text[i] = buf[i];
730 drawChar(out_text, &ruby_font, flush_flag, lookback_flag, surface, cache_info);
731 buf += n;
732 }
733 }
734 ruby_struct.stage = RubyStruct::NONE;
735 }
736
textCommand()737 int ONScripter::textCommand()
738 {
739 if (line_enter_status <= 1 && (!pretextgosub_label || saveon_flag) && internal_saveon_flag){
740 storeSaveFile();
741 internal_saveon_flag = false;
742 }
743
744 char *buf = script_h.getStringBuffer();
745
746 bool tag_flag = true;
747 unsigned short unicode = script_h.enc.getUTF16("�y", Encoding::CODE_CP932);
748 int n = script_h.enc.getBytes(buf[string_buffer_offset]);
749 if (buf[string_buffer_offset] == '[')
750 string_buffer_offset++;
751 else if (zenkakko_flag &&
752 script_h.enc.getUTF16(buf + string_buffer_offset) == unicode)
753 string_buffer_offset += n;
754 else
755 tag_flag = false;
756
757 int start_offset = string_buffer_offset;
758 int end_offset = start_offset;
759 while (tag_flag && buf[string_buffer_offset]){
760 unsigned short unicode = script_h.enc.getUTF16("�z", Encoding::CODE_CP932);
761 int n = script_h.enc.getBytes(buf[string_buffer_offset]);
762 if (zenkakko_flag &&
763 script_h.enc.getUTF16(buf + string_buffer_offset) == unicode){
764 end_offset = string_buffer_offset;
765 string_buffer_offset += n;
766 break;
767 }
768 else if (buf[string_buffer_offset] == ']'){
769 end_offset = string_buffer_offset;
770 string_buffer_offset++;
771 break;
772 }
773 else
774 string_buffer_offset += n;
775 }
776
777 if (pretextgosub_label &&
778 !last_nest_info->pretextgosub_flag &&
779 (!pagetag_flag || page_enter_status == 0) &&
780 line_enter_status == 0){
781
782 if (current_page->tag) delete[] current_page->tag;
783 if (end_offset > start_offset){
784 int len = end_offset - start_offset;
785 current_page->tag = new char[len+1];
786 memcpy(current_page->tag, buf + start_offset, len);
787 current_page->tag[len] = 0;
788 }
789 else{
790 current_page->tag = NULL;
791 }
792
793 saveon_flag = false;
794 pretext_buf = script_h.getCurrent();
795 gosubReal(pretextgosub_label, script_h.getCurrent(), false, true);
796 line_enter_status = 1;
797
798 return RET_CONTINUE;
799 }
800
801 enterTextDisplayMode();
802
803 #ifdef USE_LUA
804 if (lua_handler.isCallbackEnabled(LUAHandler::LUA_TEXT))
805 {
806 if (lua_handler.callFunction(true, "text"))
807 errorAndExit( lua_handler.error_str );
808 processEOT();
809 }
810 else
811 #endif
812 while(processText());
813
814 return RET_CONTINUE;
815 }
816
checkLineBreak(const char * buf,FontInfo * fi)817 bool ONScripter::checkLineBreak(const char *buf, FontInfo *fi)
818 {
819 if (!is_kinsoku) return false;
820
821 // check start kinsoku
822 int n = script_h.enc.getBytes(buf[0]);
823 if (isStartKinsoku(buf+n) ||
824 (buf[n]=='_' && isStartKinsoku(buf+n+1))){
825 const char *buf2 = buf + n;
826 if (buf2[0] == '_') buf2++;
827 int i = 2;
828 while (!fi->isEndOfLine(i)){
829 n = script_h.enc.getBytes(buf2[0]);
830 if (buf2[n] == 0x0a || buf2[n] == 0) break;
831 else if (script_h.enc.getBytes(buf2[n]) == 1) buf2++;
832 else if (isStartKinsoku(buf2 + n)){
833 i += 2;
834 buf2 += n;
835 }
836 else break;
837 }
838
839 if (fi->isEndOfLine(i)) return true;
840 }
841
842 // check end kinsoku
843 if (isEndKinsoku(buf)){
844 const char *buf2 = buf + n;
845 int i = 2;
846 while (!fi->isEndOfLine(i)){
847 n = script_h.enc.getBytes(buf2[0]);
848 if (buf2[n] == 0x0a || buf2[n] == 0) break;
849 else if (script_h.enc.getBytes(buf2[n]) == 1) buf2++;
850 else if (isEndKinsoku(buf2 + n)){
851 i += 2;
852 buf2 += n;
853 }
854 else break;
855 }
856
857 if (fi->isEndOfLine(i)) return true;
858 }
859
860 return false;
861 }
862
checkLigatureLineBreak(const char * buf,FontInfo * fi)863 bool ONScripter::checkLigatureLineBreak(const char *buf, FontInfo *fi)
864 {
865 if (script_h.current_language != 0) return false;
866
867 openFont(fi);
868
869 int w = 0;
870 while (buf[0] != ' ' && buf[0] != 0x0a && buf[0] != 0 &&
871 buf[0] != script_h.enc.getTextMarker()){
872 int n = script_h.enc.getBytes(buf[0]);
873 unsigned short unicode = script_h.enc.getUTF16(buf);
874
875 int minx, maxx, miny, maxy, advanced;
876 TTF_GlyphMetrics((TTF_Font*)fi->ttf_font[0], unicode,
877 &minx, &maxx, &miny, &maxy, &advanced);
878
879 w += advanced + fi->pitch_xy[0] - fi->font_size_xy[0];
880 buf += n;
881 }
882 w -= fi->pitch_xy[0] - fi->font_size_xy[0];
883
884 return fi->isEndOfLine(w * 2.0 / fi->pitch_xy[0]);
885 }
886
processEOT()887 void ONScripter::processEOT()
888 {
889 if ( skip_mode & SKIP_TO_EOL ){
890 flush( refreshMode() );
891 skip_mode &= ~SKIP_TO_EOL;
892 }
893
894 if (!sentence_font.isLineEmpty() && !new_line_skip_flag){
895 // if sentence_font.isLineEmpty() is true, newPage() might be already issued
896 if (!sentence_font.isEndOfLine()) current_page->add( 0x0a );
897 sentence_font.newLine();
898 }
899
900 if (!new_line_skip_flag && !pagetag_flag && line_enter_status == 2) line_enter_status = 0;
901 }
902
processText()903 bool ONScripter::processText()
904 {
905 //printf("textCommand %c %d %d %d\n", script_h.getStringBuffer()[ string_buffer_offset ], string_buffer_offset, event_mode, line_enter_status);
906 char out_text[4]= {};
907
908 //printf("*** textCommand %d (%d,%d)\n", string_buffer_offset, sentence_font.xy[0], sentence_font.xy[1]);
909
910 while( (!(script_h.getEndStatus() & ScriptHandler::END_1BYTE_CHAR) &&
911 script_h.getStringBuffer()[ string_buffer_offset ] == ' ') ||
912 script_h.getStringBuffer()[ string_buffer_offset ] == '\t' ) string_buffer_offset ++;
913
914 if (script_h.getStringBuffer()[string_buffer_offset] == 0x00){
915 processEOT();
916 return false;
917 }
918
919 line_enter_status = 2;
920 if (pagetag_flag) page_enter_status = 1;
921
922 new_line_skip_flag = false;
923
924 char ch = script_h.getStringBuffer()[string_buffer_offset];
925 int n = script_h.enc.getBytes(ch);
926 if (n >= 2){
927 /* ---------------------------------------- */
928 /* Kinsoku process */
929 if (checkLineBreak(script_h.getStringBuffer() + string_buffer_offset, &sentence_font)){
930 sentence_font.newLine();
931 for (int i=0 ; i<indent_offset ; i++){
932 if (script_h.enc.getEncoding() == Encoding::CODE_CP932){
933 current_page->add(0x81);
934 current_page->add(0x40);
935 }
936 else{
937 current_page->add(0xe3);
938 current_page->add(0x80);
939 current_page->add(0x80);
940 }
941 sentence_font.advanceCharInHankaku(2);
942 }
943 }
944
945 for (int i=0; i<n; i++)
946 out_text[i] = script_h.getStringBuffer()[string_buffer_offset+i];
947
948 if (script_h.checkClickstr(&script_h.getStringBuffer()[string_buffer_offset]) > 0){
949 if (sentence_font.getRemainingLine() <= clickstr_line)
950 return clickNewPage( out_text );
951 else
952 return clickWait( out_text );
953 }
954 else{
955 clickstr_state = CLICK_NONE;
956 }
957
958 if ( skip_mode || ctrl_pressed_status ){
959 drawChar( out_text, &sentence_font, false, true, accumulation_surface, &text_info );
960 }
961 else{
962 drawChar( out_text, &sentence_font, true, true, accumulation_surface, &text_info );
963
964 event_mode = WAIT_TIMER_MODE | WAIT_INPUT_MODE;
965 if ( sentence_font.wait_time == -1 )
966 waitEvent( default_text_speed[text_speed_no] );
967 else
968 waitEvent( sentence_font.wait_time );
969 }
970
971 num_chars_in_sentence++;
972 string_buffer_offset += n;
973
974 return true;
975 }
976 else if ( ch == '@' ){ // wait for click
977 return clickWait( NULL );
978 }
979 else if ( ch == '\\' ){ // new page
980 return clickNewPage( NULL );
981 }
982 else if ( ch == '_' ){ // Ignore an immediate click wait
983 string_buffer_offset++;
984
985 int matched_len = script_h.checkClickstr(script_h.getStringBuffer() + string_buffer_offset, true);
986 if (matched_len > 0){
987 out_text[0] = script_h.getStringBuffer()[string_buffer_offset];
988 if (out_text[0] != '@' && out_text[0] != '\\'){
989 for (int i=1; i<matched_len; i++)
990 out_text[i] = script_h.getStringBuffer()[string_buffer_offset+i];
991 bool flush_flag = true;
992 if ( skip_mode || ctrl_pressed_status ) flush_flag = false;
993 drawChar( out_text, &sentence_font, flush_flag, true, accumulation_surface, &text_info );
994 }
995 string_buffer_offset += matched_len;
996 }
997
998 return true;
999 }
1000 else if ( ch == '!' && !(script_h.getEndStatus() & ScriptHandler::END_1BYTE_CHAR) ){
1001 string_buffer_offset++;
1002 if ( script_h.getStringBuffer()[ string_buffer_offset ] == 's' ){
1003 string_buffer_offset++;
1004 if ( script_h.getStringBuffer()[ string_buffer_offset ] == 'd' ){
1005 sentence_font.wait_time = -1;
1006 string_buffer_offset++;
1007 }
1008 else{
1009 int t = 0;
1010 while( script_h.getStringBuffer()[ string_buffer_offset ] >= '0' &&
1011 script_h.getStringBuffer()[ string_buffer_offset ] <= '9' ){
1012 t = t*10 + script_h.getStringBuffer()[ string_buffer_offset ] - '0';
1013 string_buffer_offset++;
1014 }
1015 sentence_font.wait_time = t;
1016 while (script_h.getStringBuffer()[ string_buffer_offset ] == ' ' ||
1017 script_h.getStringBuffer()[ string_buffer_offset ] == '\t') string_buffer_offset++;
1018 }
1019 }
1020 else if ( script_h.getStringBuffer()[ string_buffer_offset ] == 'w' ||
1021 script_h.getStringBuffer()[ string_buffer_offset ] == 'd' ){
1022 bool flag = false;
1023 if ( script_h.getStringBuffer()[ string_buffer_offset ] == 'd' ) flag = true;
1024 string_buffer_offset++;
1025 int t = 0;
1026 while( script_h.getStringBuffer()[ string_buffer_offset ] >= '0' &&
1027 script_h.getStringBuffer()[ string_buffer_offset ] <= '9' ){
1028 t = t*10 + script_h.getStringBuffer()[ string_buffer_offset ] - '0';
1029 string_buffer_offset++;
1030 }
1031 while (script_h.getStringBuffer()[ string_buffer_offset ] == ' ' ||
1032 script_h.getStringBuffer()[ string_buffer_offset ] == '\t') string_buffer_offset++;
1033 if (!skip_mode && !ctrl_pressed_status){
1034 event_mode = WAIT_TIMER_MODE;
1035 if (flag) event_mode |= WAIT_INPUT_MODE;
1036 waitEvent(t);
1037 }
1038 }
1039 return true;
1040 }
1041 else if (ch == '#'){
1042 readColor( &sentence_font.color, script_h.getStringBuffer() + string_buffer_offset );
1043 readColor( &ruby_font.color, script_h.getStringBuffer() + string_buffer_offset );
1044 string_buffer_offset += 7;
1045 return true;
1046 }
1047 else if ( ch == '(' &&
1048 (!english_mode ||
1049 !(script_h.getEndStatus() & ScriptHandler::END_1BYTE_CHAR)) ){
1050 current_page->add('(');
1051 startRuby( script_h.getStringBuffer() + string_buffer_offset + 1, sentence_font );
1052
1053 string_buffer_offset++;
1054 return true;
1055 }
1056 else if ( ch == '/' && !(script_h.getEndStatus() & ScriptHandler::END_1BYTE_CHAR) ){
1057 if ( ruby_struct.stage == RubyStruct::BODY ){
1058 current_page->add('/');
1059 sentence_font.addLineOffset(ruby_struct.margin);
1060 string_buffer_offset = ruby_struct.ruby_end - script_h.getStringBuffer();
1061 if (*ruby_struct.ruby_end == ')'){
1062 if ( skip_mode || ctrl_pressed_status )
1063 endRuby(false, true, accumulation_surface, &text_info);
1064 else
1065 endRuby(true, true, accumulation_surface, &text_info);
1066 current_page->add(')');
1067 string_buffer_offset++;
1068 }
1069
1070 return true;
1071 }
1072 else{ // skip new line
1073 new_line_skip_flag = true;
1074 string_buffer_offset++;
1075 if (script_h.getStringBuffer()[string_buffer_offset] != 0x00)
1076 errorAndExit( "'new line' must follow '/'." );
1077 return true; // skip the following eol
1078 }
1079 }
1080 else if ( ch == ')' && !(script_h.getEndStatus() & ScriptHandler::END_1BYTE_CHAR) &&
1081 ruby_struct.stage == RubyStruct::BODY ){
1082 current_page->add(')');
1083 string_buffer_offset++;
1084 ruby_struct.stage = RubyStruct::NONE;
1085 return true;
1086 }
1087 else if ( ch == '<' &&
1088 (!english_mode ||
1089 !(script_h.getEndStatus() & ScriptHandler::END_1BYTE_CHAR)) ){
1090 current_page->add('<');
1091 string_buffer_offset++;
1092 int no = 0;
1093 while(script_h.getStringBuffer()[string_buffer_offset]>='0' &&
1094 script_h.getStringBuffer()[string_buffer_offset]<='9'){
1095 current_page->add(script_h.getStringBuffer()[string_buffer_offset]);
1096 no=no*10+script_h.getStringBuffer()[string_buffer_offset++]-'0';
1097 }
1098 in_textbtn_flag = true;
1099 return true;
1100 }
1101 else if ( ch == '>' && in_textbtn_flag &&
1102 (!english_mode ||
1103 !(script_h.getEndStatus() & ScriptHandler::END_1BYTE_CHAR)) ){
1104 current_page->add('>');
1105 string_buffer_offset++;
1106 in_textbtn_flag = false;
1107 return true;
1108 }
1109 else if (script_h.enc.getEncoding() == Encoding::CODE_UTF8 &&
1110 ch == script_h.enc.getTextMarker()){
1111 int status = script_h.getEndStatus();
1112 if (status & ScriptHandler::END_1BYTE_CHAR)
1113 script_h.setEndStatus(status & ~ScriptHandler::END_1BYTE_CHAR, true);
1114 else
1115 script_h.setEndStatus(ScriptHandler::END_1BYTE_CHAR);
1116 string_buffer_offset++;
1117
1118 return true;
1119 }
1120 else if (script_h.enc.getEncoding() == Encoding::CODE_UTF8 &&
1121 ch == '~'){
1122 current_page->add('~');
1123 while(1){
1124 ch = script_h.getStringBuffer()[++string_buffer_offset];
1125 if (ch == 0x0a || ch == 0) break;
1126 current_page->add(ch);
1127 if (ch == '~'){
1128 string_buffer_offset++;
1129 break;
1130 }
1131 if (ch == 'i'){
1132 openFont(&sentence_font);
1133 sentence_font.toggleStyle(TTF_STYLE_ITALIC);
1134 }
1135 else if (ch == 'b'){
1136 openFont(&sentence_font);
1137 sentence_font.toggleStyle(TTF_STYLE_BOLD);
1138 }
1139 }
1140
1141 return true;
1142 }
1143 else{
1144 out_text[0] = ch;
1145
1146 int matched_len = script_h.checkClickstr(script_h.getStringBuffer() + string_buffer_offset);
1147
1148 if (matched_len > 0){
1149 if (matched_len == 2) out_text[1] = script_h.getStringBuffer()[ string_buffer_offset + 1 ];
1150 if (sentence_font.getRemainingLine() <= clickstr_line)
1151 return clickNewPage( out_text );
1152 else
1153 return clickWait( out_text );
1154 }
1155 else if (script_h.getStringBuffer()[ string_buffer_offset + 1 ] &&
1156 script_h.checkClickstr(&script_h.getStringBuffer()[string_buffer_offset+1]) == 1 &&
1157 script_h.getEndStatus() & ScriptHandler::END_1BYTE_CHAR){
1158 if ( script_h.getStringBuffer()[ string_buffer_offset + 2 ] &&
1159 script_h.checkClickstr(&script_h.getStringBuffer()[string_buffer_offset+2]) > 0){
1160 clickstr_state = CLICK_NONE;
1161 }
1162 else if (script_h.getStringBuffer()[ string_buffer_offset + 1 ] == '@'){
1163 return clickWait( out_text );
1164 }
1165 else if (script_h.getStringBuffer()[ string_buffer_offset + 1 ] == '\\'){
1166 return clickNewPage( out_text );
1167 }
1168 else{
1169 out_text[1] = script_h.getStringBuffer()[ string_buffer_offset + 1 ];
1170 if (sentence_font.getRemainingLine() <= clickstr_line)
1171 return clickNewPage( out_text );
1172 else
1173 return clickWait( out_text );
1174 }
1175 }
1176 else{
1177 clickstr_state = CLICK_NONE;
1178 }
1179
1180 bool flush_flag = true;
1181 if ( skip_mode || ctrl_pressed_status )
1182 flush_flag = false;
1183 if (script_h.getStringBuffer()[ string_buffer_offset + 1 ] &&
1184 !(script_h.getEndStatus() & ScriptHandler::END_1BYTE_CHAR) &&
1185 script_h.enc.getEncoding() == Encoding::CODE_CP932){
1186 out_text[1] = script_h.getStringBuffer()[ string_buffer_offset + 1 ];
1187 drawChar(out_text, &sentence_font, flush_flag, true, accumulation_surface, &text_info);
1188 num_chars_in_sentence++;
1189 }
1190 else if (script_h.getEndStatus() & ScriptHandler::END_1BYTE_CHAR ||
1191 script_h.enc.getEncoding() == Encoding::CODE_UTF8){
1192 if (checkLigatureLineBreak(script_h.getStringBuffer() + string_buffer_offset, &sentence_font))
1193 sentence_font.newLine();
1194 drawChar(out_text, &sentence_font, flush_flag, true, accumulation_surface, &text_info);
1195 num_chars_in_sentence++;
1196 }
1197
1198 if (!skip_mode && !ctrl_pressed_status){
1199 event_mode = WAIT_TIMER_MODE | WAIT_INPUT_MODE;
1200 if ( sentence_font.wait_time == -1 )
1201 waitEvent( default_text_speed[text_speed_no] );
1202 else
1203 waitEvent( sentence_font.wait_time );
1204 }
1205
1206 if (script_h.getStringBuffer()[ string_buffer_offset + 1 ] &&
1207 !(script_h.getEndStatus() & ScriptHandler::END_1BYTE_CHAR) &&
1208 script_h.enc.getEncoding() == Encoding::CODE_CP932)
1209 string_buffer_offset++;
1210 string_buffer_offset++;
1211
1212 return true;
1213 }
1214
1215 return false;
1216 }
1217
1218
1219