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