removed some remaining unused X11 stuff
[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       BlitBitmapMasked(src_bitmap, dst_bitmap, src_x, src_y,
311                        font_width, font_height, dst_x, dst_y);
312     }
313     else        /* normal, non-masked font blitting */
314     {
315       BlitBitmap(src_bitmap, dst_bitmap, src_x, src_y,
316                  font_width, font_height, dst_x, dst_y);
317     }
318
319     dst_x += font_width;
320   }
321 }
322
323
324 /* ========================================================================= */
325 /* text buffer drawing functions                                             */
326 /* ========================================================================= */
327
328 #define MAX_LINES_FROM_FILE             1024
329
330 char *GetTextBufferFromFile(char *filename, int max_lines)
331 {
332   File *file;
333   char *buffer;
334   int num_lines = 0;
335
336   if (filename == NULL)
337     return NULL;
338
339   if (!(file = openFile(filename, MODE_READ)))
340     return NULL;
341
342   buffer = checked_calloc(1);   /* start with valid, but empty text buffer */
343
344   while (!checkEndOfFile(file) && num_lines < max_lines)
345   {
346     char line[MAX_LINE_LEN];
347
348     /* read next line of input file */
349     if (!getStringFromFile(file, line, MAX_LINE_LEN))
350       break;
351
352     buffer = checked_realloc(buffer, strlen(buffer) + strlen(line) + 1);
353
354     strcat(buffer, line);
355
356     num_lines++;
357   }
358
359   closeFile(file);
360
361   return buffer;
362 }
363
364 static boolean RenderLineToBuffer(char **src_buffer_ptr, char *dst_buffer,
365                                   int *dst_buffer_len, int line_length,
366                                   boolean last_line_was_empty)
367 {
368   char *text_ptr = *src_buffer_ptr;
369   char *buffer = dst_buffer;
370   int buffer_len = *dst_buffer_len;
371   boolean buffer_filled = FALSE;
372
373   while (*text_ptr)
374   {
375     char *word_ptr;
376     int word_len;
377
378     /* skip leading whitespaces */
379     while (*text_ptr == ' ' || *text_ptr == '\t')
380       text_ptr++;
381
382     word_ptr = text_ptr;
383     word_len = 0;
384
385     /* look for end of next word */
386     while (*word_ptr != ' ' && *word_ptr != '\t' && *word_ptr != '\0')
387     {
388       word_ptr++;
389       word_len++;
390     }
391
392     if (word_len == 0)
393     {
394       continue;
395     }
396     else if (*text_ptr == '\n')         /* special case: force empty line */
397     {
398       if (buffer_len == 0)
399         text_ptr++;
400
401       /* prevent printing of multiple empty lines */
402       if (buffer_len > 0 || !last_line_was_empty)
403         buffer_filled = TRUE;
404     }
405     else if (word_len < line_length - buffer_len)
406     {
407       /* word fits into text buffer -- add word */
408
409       if (buffer_len > 0)
410         buffer[buffer_len++] = ' ';
411
412       strncpy(&buffer[buffer_len], text_ptr, word_len);
413       buffer_len += word_len;
414       buffer[buffer_len] = '\0';
415       text_ptr += word_len;
416     }
417     else if (buffer_len > 0)
418     {
419       /* not enough space left for word in text buffer -- print buffer */
420
421       buffer_filled = TRUE;
422     }
423     else
424     {
425       /* word does not fit at all into empty text buffer -- cut word */
426
427       strncpy(buffer, text_ptr, line_length);
428       buffer[line_length] = '\0';
429       text_ptr += line_length;
430       buffer_filled = TRUE;
431     }
432
433     if (buffer_filled)
434       break;
435   }
436
437   *src_buffer_ptr = text_ptr;
438   *dst_buffer_len = buffer_len;
439
440   return buffer_filled;
441 }
442
443 static boolean getCheckedTokenValueFromString(char *string, char **token,
444                                               char **value)
445 {
446   char *ptr;
447
448   if (!getTokenValueFromString(string, token, value))
449     return FALSE;
450
451   if (**token != '.')                   /* token should begin with dot */
452     return FALSE;
453
454   for (ptr = *token; *ptr; ptr++)       /* token should contain no whitespace */
455     if (*ptr == ' ' || *ptr == '\t')
456       return FALSE;
457
458   for (ptr = *value; *ptr; ptr++)       /* value should contain no whitespace */
459     if (*ptr == ' ' || *ptr == '\t')
460       return FALSE;
461
462   return TRUE;
463 }
464
465 static void DrawTextBuffer_Flush(int x, int y, char *buffer, int font_nr,
466                                  int line_length, int cut_length,
467                                  int line_spacing, int mask_mode,
468                                  boolean centered, int current_line)
469 {
470   int buffer_len = strlen(buffer);
471   int font_width = getFontWidth(font_nr);
472   int font_height = getFontHeight(font_nr);
473   int offset_chars = (centered ? (line_length - buffer_len) / 2 : 0);
474   int offset_xsize =
475     (centered ? font_width * (line_length - buffer_len) / 2 : 0);
476   int final_cut_length = MAX(0, cut_length - offset_chars);
477   int xx = x + offset_xsize;
478   int yy = y + current_line * (font_height + line_spacing);
479
480   buffer[final_cut_length] = '\0';
481
482   if (mask_mode != -1)
483     DrawTextExt(drawto, xx, yy, buffer, font_nr, mask_mode);
484   else
485     DrawText(xx, yy, buffer, font_nr);
486 }
487
488 int DrawTextBuffer(int x, int y, char *text_buffer, int font_nr,
489                    int line_length, int cut_length, int max_lines,
490                    int line_spacing, int mask_mode, boolean autowrap,
491                    boolean centered, boolean parse_comments)
492 {
493   char buffer[line_length + 1];
494   int buffer_len;
495   int current_line = 0;
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_line < max_lines)
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_line < max_lines)
537         {
538           DrawTextBuffer_Flush(x, y, buffer, font_nr, line_length, cut_length,
539                                line_spacing, mask_mode, centered, current_line);
540
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
557       continue;
558     }
559
560     /* cut trailing newline and carriage return from input line */
561     for (line_ptr = line; *line_ptr; line_ptr++)
562     {
563       if (*line_ptr == '\n' || *line_ptr == '\r')
564       {
565         *line_ptr = '\0';
566         break;
567       }
568     }
569
570     if (strlen(line) == 0)              /* special case: force empty line */
571       strcpy(line, "\n");
572
573     line_ptr = line;
574
575     while (*line_ptr && current_line < max_lines)
576     {
577       boolean buffer_filled;
578
579       if (autowrap)
580       {
581         buffer_filled = RenderLineToBuffer(&line_ptr, buffer, &buffer_len,
582                                            line_length, last_line_was_empty);
583       }
584       else
585       {
586         if (strlen(line_ptr) <= line_length)
587         {
588           buffer_len = strlen(line_ptr);
589           strcpy(buffer, line_ptr);
590         }
591         else
592         {
593           buffer_len = line_length;
594           strncpy(buffer, line_ptr, line_length);
595         }
596
597         buffer[buffer_len] = '\0';
598         line_ptr += buffer_len;
599
600         buffer_filled = TRUE;
601       }
602
603       if (buffer_filled)
604       {
605         DrawTextBuffer_Flush(x, y, buffer, font_nr, line_length, cut_length,
606                              line_spacing, mask_mode, centered, current_line);
607         current_line++;
608
609         last_line_was_empty = (buffer_len == 0);
610
611         buffer[0] = '\0';
612         buffer_len = 0;
613       }
614     }
615   }
616
617   if (buffer_len > 0 && current_line < max_lines)
618   {
619     DrawTextBuffer_Flush(x, y, buffer, font_nr, line_length, cut_length,
620                          line_spacing, mask_mode, centered, current_line);
621     current_line++;
622   }
623
624   return current_line;
625 }
626
627 int DrawTextBufferVA(int x, int y, char *format, va_list ap, int font_nr,
628                      int line_length, int cut_length, int max_lines,
629                      int line_spacing, int mask_mode, boolean autowrap,
630                      boolean centered, boolean parse_comments)
631 {
632   char text_buffer[MAX_OUTPUT_LINESIZE];
633   int text_length = vsnprintf(text_buffer, MAX_OUTPUT_LINESIZE, format, ap);
634
635   if (text_length >= MAX_OUTPUT_LINESIZE)
636     Error(ERR_WARN, "string too long in DrawTextBufferVA() -- truncated");
637
638   int num_lines_printed = DrawTextBuffer(x, y, text_buffer, font_nr,
639                                          line_length, cut_length, max_lines,
640                                          line_spacing, mask_mode, autowrap,
641                                          centered, parse_comments);
642   return num_lines_printed;
643 }
644
645 int DrawTextFile(int x, int y, char *filename, int font_nr,
646                  int line_length, int cut_length, int max_lines,
647                  int line_spacing, int mask_mode, boolean autowrap,
648                  boolean centered, boolean parse_comments)
649 {
650   char *text_buffer = GetTextBufferFromFile(filename, MAX_LINES_FROM_FILE);
651   int num_lines_printed = DrawTextBuffer(x, y, text_buffer, font_nr,
652                                          line_length, cut_length, max_lines,
653                                          line_spacing, mask_mode, autowrap,
654                                          centered, parse_comments);
655   checked_free(text_buffer);
656
657   return num_lines_printed;
658 }