added enabling/disabling virtual button overlay depending on input events
[rocksndiamonds.git] / src / libgame / text.c
1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
5 //                  Holger Schemel
6 //                  info@artsoft.org
7 //                  http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
9 // text.c
10 // ============================================================================
11
12 #include <stdio.h>
13 #include <stdarg.h>
14
15 #include "text.h"
16 #include "misc.h"
17
18
19 /* ========================================================================= */
20 /* font functions                                                            */
21 /* ========================================================================= */
22
23 void InitFontInfo(struct FontBitmapInfo *font_bitmap_info, int num_fonts,
24                   int (*select_font_function)(int),
25                   int (*get_font_from_token_function)(char *))
26 {
27   gfx.num_fonts = num_fonts;
28   gfx.font_bitmap_info = font_bitmap_info;
29   gfx.select_font_function = select_font_function;
30   gfx.get_font_from_token_function = get_font_from_token_function;
31 }
32
33 void FreeFontInfo(struct FontBitmapInfo *font_bitmap_info)
34 {
35   if (font_bitmap_info == NULL)
36     return;
37
38   free(font_bitmap_info);
39 }
40
41 struct FontBitmapInfo *getFontBitmapInfo(int font_nr)
42 {
43   int font_bitmap_id = gfx.select_font_function(font_nr);
44
45   return &gfx.font_bitmap_info[font_bitmap_id];
46 }
47
48 int getFontWidth(int font_nr)
49 {
50   int font_bitmap_id = gfx.select_font_function(font_nr);
51
52   return gfx.font_bitmap_info[font_bitmap_id].width;
53 }
54
55 int getFontHeight(int font_nr)
56 {
57   int font_bitmap_id = gfx.select_font_function(font_nr);
58
59   return gfx.font_bitmap_info[font_bitmap_id].height;
60 }
61
62 int getFontDrawOffsetX(int font_nr)
63 {
64   int font_bitmap_id = gfx.select_font_function(font_nr);
65
66   return gfx.font_bitmap_info[font_bitmap_id].draw_xoffset;
67 }
68
69 int getFontDrawOffsetY(int font_nr)
70 {
71   int font_bitmap_id = gfx.select_font_function(font_nr);
72
73   return gfx.font_bitmap_info[font_bitmap_id].draw_yoffset;
74 }
75
76 int getTextWidth(char *text, int font_nr)
77 {
78   return (text != NULL ? strlen(text) * getFontWidth(font_nr) : 0);
79 }
80
81 static int getFontCharPosition(int font_nr, char c)
82 {
83   int font_bitmap_id = gfx.select_font_function(font_nr);
84   struct FontBitmapInfo *font = &gfx.font_bitmap_info[font_bitmap_id];
85   boolean default_font = (font->num_chars == DEFAULT_NUM_CHARS_PER_FONT);
86   int font_pos = (unsigned char)c - 32;
87
88   /* map some special characters to their ascii values in default font */
89   if (default_font)
90     font_pos = MAP_FONT_ASCII(c) - 32;
91
92   /* this allows dynamic special characters together with special font */
93   if (font_pos < 0 || font_pos >= font->num_chars)
94     font_pos = 0;
95
96   return font_pos;
97 }
98
99 void getFontCharSource(int font_nr, char c, Bitmap **bitmap, int *x, int *y)
100 {
101   int font_bitmap_id = gfx.select_font_function(font_nr);
102   struct FontBitmapInfo *font = &gfx.font_bitmap_info[font_bitmap_id];
103   int font_pos = getFontCharPosition(font_nr, c);
104
105   *bitmap = font->bitmap;
106   *x = font->src_x + (font_pos % font->num_chars_per_line) * font->width;
107   *y = font->src_y + (font_pos / font->num_chars_per_line) * font->height;
108 }
109
110
111 /* ========================================================================= */
112 /* text string helper functions                                              */
113 /* ========================================================================= */
114
115 int maxWordLengthInString(char *text)
116 {
117   char *text_ptr;
118   int word_len = 0, max_word_len = 0;
119
120   for (text_ptr = text; *text_ptr; text_ptr++)
121   {
122     word_len = (*text_ptr != ' ' ? word_len + 1 : 0);
123
124     max_word_len = MAX(word_len, max_word_len);
125   }
126
127   return max_word_len;
128 }
129
130
131 /* ========================================================================= */
132 /* simple text drawing functions                                             */
133 /* ========================================================================= */
134
135 void DrawInitText(char *text, int ypos, int font_nr)
136 {
137   LimitScreenUpdates(TRUE);
138
139   UPDATE_BUSY_STATE();
140
141   if (window != NULL &&
142       gfx.draw_init_text &&
143       gfx.num_fonts > 0 &&
144       gfx.font_bitmap_info[font_nr].bitmap != NULL)
145   {
146     int x = (video.width - getTextWidth(text, font_nr)) / 2;
147     int y = ypos;
148     int width = video.width;
149     int height = getFontHeight(font_nr);
150
151     ClearRectangle(drawto, 0, y, width, height);
152     DrawTextExt(drawto, x, y, text, font_nr, BLIT_OPAQUE);
153
154     BlitBitmap(drawto, window, 0, 0, video.width, video.height, 0, 0);
155   }
156 }
157
158 void DrawTextF(int x, int y, int font_nr, char *format, ...)
159 {
160   char buffer[MAX_OUTPUT_LINESIZE + 1];
161   va_list ap;
162
163   va_start(ap, format);
164   vsprintf(buffer, format, ap);
165   va_end(ap);
166
167   if (strlen(buffer) > MAX_OUTPUT_LINESIZE)
168     Error(ERR_EXIT, "string too long in DrawTextF() -- aborting");
169
170   DrawText(gfx.sx + x, gfx.sy + y, buffer, font_nr);
171 }
172
173 void DrawTextFCentered(int y, int font_nr, char *format, ...)
174 {
175   char buffer[MAX_OUTPUT_LINESIZE + 1];
176   va_list ap;
177
178   va_start(ap, format);
179   vsprintf(buffer, format, ap);
180   va_end(ap);
181
182   if (strlen(buffer) > MAX_OUTPUT_LINESIZE)
183     Error(ERR_EXIT, "string too long in DrawTextFCentered() -- aborting");
184
185   DrawText(gfx.sx + (gfx.sxsize - getTextWidth(buffer, font_nr)) / 2,
186            gfx.sy + y, buffer, font_nr);
187 }
188
189 void DrawTextS(int x, int y, int font_nr, char *text)
190 {
191   DrawText(gfx.sx + x, gfx.sy + y, text, font_nr);
192 }
193
194 void DrawTextSCentered(int y, int font_nr, char *text)
195 {
196   DrawText(gfx.sx + (gfx.sxsize - getTextWidth(text, font_nr)) / 2,
197            gfx.sy + y, text, font_nr);
198 }
199
200 void DrawTextSAligned(int x, int y, char *text, int font_nr, int align)
201 {
202   DrawText(gfx.sx + ALIGNED_XPOS(x, getTextWidth(text, font_nr), align),
203            gfx.sx + y, text, font_nr);
204 }
205
206 void DrawTextAligned(int x, int y, char *text, int font_nr, int align)
207 {
208   DrawText(ALIGNED_XPOS(x, getTextWidth(text, font_nr), align),
209            y, text, font_nr);
210 }
211
212 void DrawText(int x, int y, char *text, int font_nr)
213 {
214   int mask_mode = BLIT_OPAQUE;
215
216   if (DrawingOnBackground(x, y))
217     mask_mode = BLIT_ON_BACKGROUND;
218
219   DrawTextExt(drawto, x, y, text, font_nr, mask_mode);
220
221   if (IN_GFX_FIELD_FULL(x, y))
222     redraw_mask |= REDRAW_FIELD;
223   else if (IN_GFX_DOOR_1(x, y))
224     redraw_mask |= REDRAW_DOOR_1;
225   else if (IN_GFX_DOOR_2(x, y))
226     redraw_mask |= REDRAW_DOOR_2;
227   else if (IN_GFX_DOOR_3(x, y))
228     redraw_mask |= REDRAW_DOOR_3;
229   else
230     redraw_mask |= REDRAW_ALL;
231 }
232
233 void DrawTextExt(DrawBuffer *dst_bitmap, int dst_x, int dst_y, char *text,
234                  int font_nr, int mask_mode)
235 {
236   struct FontBitmapInfo *font = getFontBitmapInfo(font_nr);
237   int font_width = getFontWidth(font_nr);
238   int font_height = getFontHeight(font_nr);
239   Bitmap *src_bitmap;
240   int src_x, src_y;
241   char *text_ptr = text;
242
243   if (font->bitmap == NULL)
244     return;
245
246   /* skip text to be printed outside the window (left/right will be clipped) */
247   if (dst_y < 0 || dst_y + font_height > video.height)
248     return;
249
250   /* add offset for drawing font characters */
251   dst_x += font->draw_xoffset;
252   dst_y += font->draw_yoffset;
253
254   while (*text_ptr)
255   {
256     char c = *text_ptr++;
257
258     if (c == '\n')
259       c = ' ';          /* print space instead of newline */
260
261     getFontCharSource(font_nr, c, &src_bitmap, &src_x, &src_y);
262
263     /* clip text at the left side of the window */
264     if (dst_x < 0)
265     {
266       dst_x += font_width;
267
268       continue;
269     }
270
271     /* clip text at the right side of the window */
272     if (dst_x + font_width > video.width)
273       break;
274
275     if (mask_mode == BLIT_INVERSE)      /* special mode for text gadgets */
276     {
277       /* first step: draw solid colored rectangle (use "cursor" character) */
278       if (strlen(text) == 1)    /* only one char inverted => draw cursor */
279       {
280         Bitmap *cursor_bitmap;
281         int cursor_x, cursor_y;
282
283         getFontCharSource(font_nr, FONT_ASCII_CURSOR, &cursor_bitmap,
284                           &cursor_x, &cursor_y);
285
286         BlitBitmap(cursor_bitmap, dst_bitmap, cursor_x, cursor_y,
287                    font_width, font_height, dst_x, dst_y);
288       }
289
290       /* second step: draw masked inverted character */
291       SDLCopyInverseMasked(src_bitmap, dst_bitmap, src_x, src_y,
292                            font_width, font_height, dst_x, dst_y);
293     }
294     else if (mask_mode == BLIT_MASKED || mask_mode == BLIT_ON_BACKGROUND)
295     {
296       if (mask_mode == BLIT_ON_BACKGROUND)
297       {
298         /* clear font character background */
299         ClearRectangleOnBackground(dst_bitmap, dst_x, dst_y,
300                                    font_width, font_height);
301       }
302
303       BlitBitmapMasked(src_bitmap, dst_bitmap, src_x, src_y,
304                        font_width, font_height, dst_x, dst_y);
305     }
306     else        /* normal, non-masked font blitting */
307     {
308       BlitBitmap(src_bitmap, dst_bitmap, src_x, src_y,
309                  font_width, font_height, dst_x, dst_y);
310     }
311
312     dst_x += font_width;
313   }
314 }
315
316
317 /* ========================================================================= */
318 /* text buffer drawing functions                                             */
319 /* ========================================================================= */
320
321 #define MAX_LINES_FROM_FILE             1024
322
323 char *GetTextBufferFromFile(char *filename, int max_lines)
324 {
325   File *file;
326   char *buffer;
327   int num_lines = 0;
328
329   if (filename == NULL)
330     return NULL;
331
332   if (!(file = openFile(filename, MODE_READ)))
333     return NULL;
334
335   buffer = checked_calloc(1);   /* start with valid, but empty text buffer */
336
337   while (!checkEndOfFile(file) && num_lines < max_lines)
338   {
339     char line[MAX_LINE_LEN];
340
341     /* read next line of input file */
342     if (!getStringFromFile(file, line, MAX_LINE_LEN))
343       break;
344
345     buffer = checked_realloc(buffer, strlen(buffer) + strlen(line) + 1);
346
347     strcat(buffer, line);
348
349     num_lines++;
350   }
351
352   closeFile(file);
353
354   return buffer;
355 }
356
357 static boolean RenderLineToBuffer(char **src_buffer_ptr, char *dst_buffer,
358                                   int *dst_buffer_len, int line_length,
359                                   boolean last_line_was_empty)
360 {
361   char *text_ptr = *src_buffer_ptr;
362   char *buffer = dst_buffer;
363   int buffer_len = *dst_buffer_len;
364   boolean buffer_filled = FALSE;
365
366   while (*text_ptr)
367   {
368     char *word_ptr;
369     int word_len;
370
371     /* skip leading whitespaces */
372     while (*text_ptr == ' ' || *text_ptr == '\t')
373       text_ptr++;
374
375     word_ptr = text_ptr;
376     word_len = 0;
377
378     /* look for end of next word */
379     while (*word_ptr != ' ' && *word_ptr != '\t' && *word_ptr != '\0')
380     {
381       word_ptr++;
382       word_len++;
383     }
384
385     if (word_len == 0)
386     {
387       continue;
388     }
389     else if (*text_ptr == '\n')         /* special case: force empty line */
390     {
391       if (buffer_len == 0)
392         text_ptr++;
393
394       /* prevent printing of multiple empty lines */
395       if (buffer_len > 0 || !last_line_was_empty)
396         buffer_filled = TRUE;
397     }
398     else if (word_len < line_length - buffer_len)
399     {
400       /* word fits into text buffer -- add word */
401
402       if (buffer_len > 0)
403         buffer[buffer_len++] = ' ';
404
405       strncpy(&buffer[buffer_len], text_ptr, word_len);
406       buffer_len += word_len;
407       buffer[buffer_len] = '\0';
408       text_ptr += word_len;
409     }
410     else if (buffer_len > 0)
411     {
412       /* not enough space left for word in text buffer -- print buffer */
413
414       buffer_filled = TRUE;
415     }
416     else
417     {
418       /* word does not fit at all into empty text buffer -- cut word */
419
420       strncpy(buffer, text_ptr, line_length);
421       buffer[line_length] = '\0';
422       text_ptr += line_length;
423       buffer_filled = TRUE;
424     }
425
426     if (buffer_filled)
427       break;
428   }
429
430   *src_buffer_ptr = text_ptr;
431   *dst_buffer_len = buffer_len;
432
433   return buffer_filled;
434 }
435
436 static boolean getCheckedTokenValueFromString(char *string, char **token,
437                                               char **value)
438 {
439   char *ptr;
440
441   if (!getTokenValueFromString(string, token, value))
442     return FALSE;
443
444   if (**token != '.')                   /* token should begin with dot */
445     return FALSE;
446
447   for (ptr = *token; *ptr; ptr++)       /* token should contain no whitespace */
448     if (*ptr == ' ' || *ptr == '\t')
449       return FALSE;
450
451   for (ptr = *value; *ptr; ptr++)       /* value should contain no whitespace */
452     if (*ptr == ' ' || *ptr == '\t')
453       return FALSE;
454
455   return TRUE;
456 }
457
458 static void DrawTextBuffer_Flush(int x, int y, char *buffer, int font_nr,
459                                  int line_length, int cut_length,
460                                  int line_spacing, int mask_mode,
461                                  boolean centered, int current_line)
462 {
463   int buffer_len = strlen(buffer);
464   int font_width = getFontWidth(font_nr);
465   int font_height = getFontHeight(font_nr);
466   int offset_chars = (centered ? (line_length - buffer_len) / 2 : 0);
467   int offset_xsize =
468     (centered ? font_width * (line_length - buffer_len) / 2 : 0);
469   int final_cut_length = MAX(0, cut_length - offset_chars);
470   int xx = x + offset_xsize;
471   int yy = y + current_line * (font_height + line_spacing);
472
473   buffer[final_cut_length] = '\0';
474
475   if (mask_mode != -1)
476     DrawTextExt(drawto, xx, yy, buffer, font_nr, mask_mode);
477   else
478     DrawText(xx, yy, buffer, font_nr);
479 }
480
481 int DrawTextBuffer(int x, int y, char *text_buffer, int font_nr,
482                    int line_length, int cut_length, int max_lines,
483                    int line_spacing, int mask_mode, boolean autowrap,
484                    boolean centered, boolean parse_comments)
485 {
486   char buffer[line_length + 1];
487   int buffer_len;
488   int current_line = 0;
489
490   if (text_buffer == NULL || *text_buffer == '\0')
491     return 0;
492
493   if (current_line >= max_lines)
494     return 0;
495
496   if (cut_length == -1)
497     cut_length = line_length;
498
499   buffer[0] = '\0';
500   buffer_len = 0;
501
502   while (*text_buffer && current_line < max_lines)
503   {
504     char line[MAX_LINE_LEN + 1];
505     char *line_ptr;
506     boolean last_line_was_empty = TRUE;
507     int num_line_chars = MAX_LINE_LEN;
508     int i;
509
510     /* copy next line from text buffer to line buffer (nearly fgets() style) */
511     for (i = 0; i < num_line_chars && *text_buffer; i++)
512       if ((line[i] = *text_buffer++) == '\n')
513         break;
514     line[i] = '\0';
515
516     /* prevent 'num_line_chars' sized lines to cause additional empty line */
517     if (i == num_line_chars && *text_buffer == '\n')
518       text_buffer++;
519
520     /* skip comments (lines directly beginning with '#') */
521     if (line[0] == '#' && parse_comments)
522     {
523       char *token, *value;
524
525       /* try to read generic token/value pair definition after comment sign */
526       if (getCheckedTokenValueFromString(line + 1, &token, &value))
527       {
528         /* if found, flush the current buffer, if non-empty */
529         if (buffer_len > 0 && current_line < max_lines)
530         {
531           DrawTextBuffer_Flush(x, y, buffer, font_nr, line_length, cut_length,
532                                line_spacing, mask_mode, centered, current_line);
533
534           current_line++;
535
536           buffer[0] = '\0';
537           buffer_len = 0;
538         }
539
540         if (strEqual(token, ".font"))
541           font_nr = gfx.get_font_from_token_function(value);
542         else if (strEqual(token, ".autowrap"))
543           autowrap = get_boolean_from_string(value);
544         else if (strEqual(token, ".centered"))
545           centered = get_boolean_from_string(value);
546         else if (strEqual(token, ".parse_comments"))
547           parse_comments = get_boolean_from_string(value);
548       }
549
550       continue;
551     }
552
553     /* cut trailing newline and carriage return from input line */
554     for (line_ptr = line; *line_ptr; line_ptr++)
555     {
556       if (*line_ptr == '\n' || *line_ptr == '\r')
557       {
558         *line_ptr = '\0';
559         break;
560       }
561     }
562
563     if (strlen(line) == 0)              /* special case: force empty line */
564       strcpy(line, "\n");
565
566     line_ptr = line;
567
568     while (*line_ptr && current_line < max_lines)
569     {
570       boolean buffer_filled;
571
572       if (autowrap)
573       {
574         buffer_filled = RenderLineToBuffer(&line_ptr, buffer, &buffer_len,
575                                            line_length, last_line_was_empty);
576       }
577       else
578       {
579         if (strlen(line_ptr) <= line_length)
580         {
581           buffer_len = strlen(line_ptr);
582           strcpy(buffer, line_ptr);
583         }
584         else
585         {
586           buffer_len = line_length;
587           strncpy(buffer, line_ptr, line_length);
588         }
589
590         buffer[buffer_len] = '\0';
591         line_ptr += buffer_len;
592
593         buffer_filled = TRUE;
594       }
595
596       if (buffer_filled)
597       {
598         DrawTextBuffer_Flush(x, y, buffer, font_nr, line_length, cut_length,
599                              line_spacing, mask_mode, centered, current_line);
600         current_line++;
601
602         last_line_was_empty = (buffer_len == 0);
603
604         buffer[0] = '\0';
605         buffer_len = 0;
606       }
607     }
608   }
609
610   if (buffer_len > 0 && current_line < max_lines)
611   {
612     DrawTextBuffer_Flush(x, y, buffer, font_nr, line_length, cut_length,
613                          line_spacing, mask_mode, centered, current_line);
614     current_line++;
615   }
616
617   return current_line;
618 }
619
620 int DrawTextBufferVA(int x, int y, char *format, va_list ap, int font_nr,
621                      int line_length, int cut_length, int max_lines,
622                      int line_spacing, int mask_mode, boolean autowrap,
623                      boolean centered, boolean parse_comments)
624 {
625   char text_buffer[MAX_OUTPUT_LINESIZE];
626   int text_length = vsnprintf(text_buffer, MAX_OUTPUT_LINESIZE, format, ap);
627
628   if (text_length >= MAX_OUTPUT_LINESIZE)
629     Error(ERR_WARN, "string too long in DrawTextBufferVA() -- truncated");
630
631   int num_lines_printed = DrawTextBuffer(x, y, text_buffer, font_nr,
632                                          line_length, cut_length, max_lines,
633                                          line_spacing, mask_mode, autowrap,
634                                          centered, parse_comments);
635   return num_lines_printed;
636 }
637
638 int DrawTextFile(int x, int y, char *filename, int font_nr,
639                  int line_length, int cut_length, int max_lines,
640                  int line_spacing, int mask_mode, boolean autowrap,
641                  boolean centered, boolean parse_comments)
642 {
643   char *text_buffer = GetTextBufferFromFile(filename, MAX_LINES_FROM_FILE);
644   int num_lines_printed = DrawTextBuffer(x, y, text_buffer, font_nr,
645                                          line_length, cut_length, max_lines,
646                                          line_spacing, mask_mode, autowrap,
647                                          centered, parse_comments);
648   checked_free(text_buffer);
649
650   return num_lines_printed;
651 }