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