1 /**********************************************************************
2  Freeciv - Copyright (C) 1996 - A Kjeldberg, L Gregersen, P Unold
3    This program is free software; you can redistribute it and/or modify
4    it under the terms of the GNU General Public License as published by
5    the Free Software Foundation; either version 2, or (at your option)
6    any later version.
7 
8    This program is distributed in the hope that it will be useful,
9    but WITHOUT ANY WARRANTY; without even the implied warranty of
10    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11    GNU General Public License for more details.
12 ***********************************************************************/
13 
14 /***************************************************************************
15                           gui_string.c  -  description
16                              -------------------
17     begin                : June 30 2002
18     copyright            : (C) 2002 by Rafał Bursig
19     email                : Rafał Bursig <bursig@poczta.fm>
20  ***************************************************************************/
21 
22 #ifdef HAVE_CONFIG_H
23 #include <fc_config.h>
24 #endif
25 
26 /* SDL */
27 #include <SDL/SDL.h>
28 
29 /* utility */
30 #include "fcintl.h"
31 #include "log.h"
32 #include "mem.h"
33 
34 /* client/gui-sdl */
35 #include "colors.h"
36 #include "graphics.h"
37 #include "gui_iconv.h"
38 #include "gui_main.h"
39 #include "themespec.h"
40 #include "unistring.h"
41 
42 #include "gui_string.h"
43 
44 /* =================================================== */
45 
46 static struct TTF_Font_Chain {
47   struct TTF_Font_Chain *next;
48   TTF_Font *font;
49   Uint16 ptsize;		/* size of font */
50   Uint16 count;			/* number of strings alliased with this font */
51 } *Font_TAB = NULL;
52 
53 static unsigned int Sizeof_Font_TAB;
54 static char *pFont_with_FullPath = NULL;
55 
56 static TTF_Font *load_font(Uint16 ptsize);
57 
58 static SDL_Surface *create_str16_surf(SDL_String16 * pString);
59 static SDL_Surface *create_str16_multi_surf(SDL_String16 * pString);
60 
61 /**************************************************************************
62   Adjust font sizes for small screen.
63 **************************************************************************/
64 #ifdef SMALL_SCREEN
adj_font(int size)65 int adj_font(int size) {
66   switch(size) {
67     case 24:
68       return 12;
69     case 20:
70       return 12;
71     case 16:
72       return 10;
73     case 14:
74       return 8;
75     case 13:
76       return 8;
77     case 12:
78       return 8;
79     case 11:
80       return 7;
81     case 10:
82       return 7;
83     case 8:
84       return 6;
85     default:
86       return size;
87   }
88 }
89 #endif /* SMALL_SCREEN */
90 
91 /**************************************************************************
92   Calculate display size of string.
93 **************************************************************************/
str16size(SDL_String16 * pString16)94 SDL_Rect str16size(SDL_String16 *pString16)
95 {
96   SDL_Rect Ret = {0, 0, 0, 0};
97 
98   if (pString16 && pString16->text && pString16->text[0] != '\0') {
99     Uint16 *pStr16 = pString16->text;
100     Uint16 c = *pStr16;
101     bool new_line = FALSE;
102     int w, h;
103 
104     /* find '\n' */
105     while (c != '\0') {
106       if (c == 10) {
107 	new_line = TRUE;
108 	break;
109       }
110       pStr16++;
111       c = *pStr16;
112     }
113 
114     if (!((pString16->style & 0x0F) & TTF_STYLE_NORMAL)) {
115       TTF_SetFontStyle(pString16->font, (pString16->style & 0x0F));
116     }
117 
118     if (new_line) {
119       int ww, hh, count = 0;
120       Uint16 **UniTexts = create_new_line_unistrings(pString16->text);
121 
122       w = 0;
123       h = 0;
124       while (UniTexts[count]) {
125         if (TTF_SizeUNICODE(pString16->font, UniTexts[count], &ww, &hh) < 0) {
126           do {
127 	    FC_FREE(UniTexts[count]);
128             count++;
129 	  } while(UniTexts[count]);
130           log_error("TTF_SizeUNICODE return ERROR !");
131         }
132         w = MAX(w, ww);
133         h += hh;
134         FC_FREE(UniTexts[count]);
135         count++;
136       }
137     } else {
138       if (TTF_SizeUNICODE(pString16->font, pString16->text, &w, &h) < 0) {
139         log_error("TTF_SizeUNICODE return ERROR !");
140       }
141     }
142 
143     if (!((pString16->style & 0x0F) & TTF_STYLE_NORMAL)) {
144       TTF_SetFontStyle(pString16->font, TTF_STYLE_NORMAL);
145     }
146 
147     Ret.w = w;
148     Ret.h = h;
149   } else {
150     Ret.h = (pString16 ? TTF_FontHeight(pString16->font) : 0);
151   }
152 
153   return Ret;
154 }
155 
156 /**************************************************************************
157   Create string16 struct with ptsize font.
158   Font will be loaded or aliased with existing font of that size.
159   pInTextString must be allocated in memory (MALLOC/fc_calloc)
160 **************************************************************************/
create_string16(Uint16 * pInTextString,size_t n_alloc,Uint16 ptsize)161 SDL_String16 *create_string16(Uint16 *pInTextString,
162                               size_t n_alloc, Uint16 ptsize)
163 {
164   SDL_String16 *str = fc_calloc(1, sizeof(SDL_String16));
165 
166   if (!ptsize) {
167     str->ptsize = theme_default_font_size(theme);
168   } else {
169     str->ptsize = ptsize;
170   }
171 
172   if ((str->font = load_font(str->ptsize)) == NULL) {
173     log_error("create_string16: load_font failed");
174     FC_FREE(str);
175 
176     return NULL;
177   }
178 
179   str->style = TTF_STYLE_NORMAL;
180   str->bgcol = (SDL_Color) {0, 0, 0, 0};
181   str->fgcol = *get_theme_color(COLOR_THEME_TEXT);
182   str->render = 2;
183 
184   /* pInTextString must be allocated in memory (MALLOC/fc_calloc) */
185   str->text = pInTextString;
186   str->n_alloc = n_alloc;
187 
188   return str;
189 }
190 
191 /**************************************************************************
192   Convert char array to SDL_String16. Pointer to target string is needed
193   as parameter, but also returned for convenience.
194 **************************************************************************/
copy_chars_to_string16(SDL_String16 * pString,const char * pCharString)195 SDL_String16 * copy_chars_to_string16(SDL_String16 *pString,
196                                       const char *pCharString)
197 {
198   size_t n;
199 
200   fc_assert_ret_val(pString != NULL, NULL);
201   fc_assert_ret_val(pCharString != NULL, NULL);
202 
203   n = (strlen(pCharString) + 1) * 2;
204 
205   if (n > pString->n_alloc) {
206     /* allocated more if this is only a small increase on before: */
207     size_t n1 = (3 * pString->n_alloc) / 2;
208     pString->n_alloc = (n > n1) ? n : n1;
209     pString->text = fc_realloc(pString->text, pString->n_alloc);
210   }
211 
212   convertcopy_to_utf16(pString->text, pString->n_alloc, pCharString);
213 
214   return pString;
215 }
216 
217 /**************************************************************************
218   Blit text to surface.
219 **************************************************************************/
write_text16(SDL_Surface * pDest,Sint16 x,Sint16 y,SDL_String16 * pString)220 int write_text16(SDL_Surface * pDest, Sint16 x, Sint16 y,
221 		 SDL_String16 * pString)
222 {
223   SDL_Rect dst_rect = { x, y, 0, 0 };
224   SDL_Surface *pText = create_text_surf_from_str16(pString);
225 
226   if (alphablit(pText, NULL, pDest, &dst_rect) < 0) {
227     log_error("write_text16: couldn't blit text to display: %s",
228               SDL_GetError());
229     FREESURFACE(pText);
230     return -1;
231   }
232 
233   FREESURFACE(pText);
234   return 0;
235 }
236 
237 /**************************************************************************
238   Create Text Surface from SDL_String16
239 **************************************************************************/
create_str16_surf(SDL_String16 * pString)240 static SDL_Surface *create_str16_surf(SDL_String16 * pString)
241 {
242   SDL_Surface *pText = NULL; /* FIXME: possibly uninitialized */
243 
244   if (!pString) {
245     return NULL;
246   }
247 
248   if (!((pString->style & 0x0F) & TTF_STYLE_NORMAL)) {
249     TTF_SetFontStyle(pString->font, (pString->style & 0x0F));
250   }
251 
252   switch (pString->render) {
253   case 0:
254     pText = TTF_RenderUNICODE_Shaded(pString->font,
255 				     pString->text, pString->fgcol,
256 				     pString->bgcol);
257     break;
258   case 1:
259   {
260     SDL_Surface *pTmp = TTF_RenderUNICODE_Solid(pString->font,
261 				    pString->text, pString->fgcol);
262 
263     if ((pText = SDL_DisplayFormat(pTmp)) == NULL) {
264       log_error("SDL_create_str16_surf: couldn't convert text "
265                 "to display format: %s", SDL_GetError());
266       pText = pTmp;
267     } else {
268       FREESURFACE( pTmp );
269     }
270 
271   }
272   break;
273   case 2:
274     pText = TTF_RenderUNICODE_Blended(pString->font,
275 				      pString->text, pString->fgcol);
276     break;
277   }
278 
279   if (pText != NULL) {
280     log_debug("SDL_create_str16_surf: Font is generally %d big, and "
281               "string is %d big", TTF_FontHeight(pString->font), pText->h);
282     log_debug("SDL_create_str16_surf: String is %d length", pText->w);
283   } else {
284     log_debug("SDL_create_str16_surf: pText NULL");
285     pText = create_surf_alpha(0, 0, SDL_SWSURFACE);
286   }
287 
288   if (!((pString->style & 0x0F) & TTF_STYLE_NORMAL)) {
289     TTF_SetFontStyle(pString->font, TTF_STYLE_NORMAL);
290   }
291 
292   return pText;
293 }
294 
295 /**************************************************************************
296   Create surface with multiline text drawn.
297 **************************************************************************/
create_str16_multi_surf(SDL_String16 * pString)298 static SDL_Surface *create_str16_multi_surf(SDL_String16 * pString)
299 {
300   SDL_Rect des = {0, 0, 0, 0};
301   SDL_Surface *pText = NULL, **pTmp = NULL;
302   Uint16 i, w = 0, count = 0;
303   Uint32 color;
304   Uint16 *pBuf = pString->text;
305   Uint16 **UniTexts = create_new_line_unistrings(pString->text);
306 
307   while (UniTexts[count]) {
308     count++;
309   }
310 
311   pTmp = fc_calloc(count, sizeof(SDL_Surface *));
312 
313   for (i = 0; i < count; i++) {
314     pString->text = UniTexts[i];
315     pTmp[i] = create_str16_surf(pString);
316   }
317 
318   pString->text = pBuf;
319 
320   /* find max len */
321   for (i = 0; i < count; i++) {
322     if (pTmp[i]->w > w) {
323       w = pTmp[i]->w;
324     }
325   }
326 
327   /* create and fill surface */
328 
329   color = pTmp[0]->format->colorkey;
330 
331   switch (pString->render) {
332   case 1:
333     pText = create_surf(w, count * pTmp[0]->h, SDL_SWSURFACE);
334     SDL_FillRect(pText, NULL, color);
335     SDL_SetColorKey(pText, SDL_SRCCOLORKEY, color);
336     break;
337   case 2:
338       pText = create_surf_with_format(pTmp[0]->format,
339 				     w, count * pTmp[0]->h, pTmp[0]->flags);
340       SDL_FillRect(pText, NULL, color);
341     break;
342   default:
343     pText = create_surf(w, count * pTmp[0]->h, SDL_SWSURFACE);
344     SDL_FillRect(pText, NULL, color);
345     break;
346   }
347 
348   /* blit (default: center left) */
349   for (i = 0; i < count; i++) {
350     if (pString->style & SF_CENTER) {
351       des.x = (w - pTmp[i]->w) / 2;
352     } else {
353       if (pString->style & SF_CENTER_RIGHT) {
354 	des.x = w - pTmp[i]->w;
355       } else {
356 	des.x = 0;
357       }
358     }
359 
360     alphablit(pTmp[i], NULL, pText, &des);
361     des.y += pTmp[i]->h;
362   }
363 
364 
365   /* Free Memmory */
366   for (i = 0; i < count; i++) {
367     FC_FREE(UniTexts[i]);
368     FREESURFACE(pTmp[i]);
369   }
370 
371   FC_FREE(pTmp);
372 
373   return pText;
374 }
375 
376 /**************************************************************************
377   Generic function to create surface with any kind of text, single line or
378   multiline, drawn.
379 **************************************************************************/
create_text_surf_from_str16(SDL_String16 * pString)380 SDL_Surface *create_text_surf_from_str16(SDL_String16 *pString)
381 {
382   if (pString && pString->text) {
383     Uint16 *pStr16 = pString->text;
384     Uint16 c = *pStr16;
385 
386     /* find '\n' */
387     while (c != '\0') {
388       if (c == 10) {
389         return create_str16_multi_surf(pString);
390       }
391       pStr16++;
392       c = *pStr16;
393     }
394 
395     return create_str16_surf(pString);
396   }
397 
398   return NULL;
399 }
400 
401 #if 0
402 /**************************************************************************
403   ...
404 **************************************************************************/
405 SDL_Surface * create_text_surf_smaller_that_w(SDL_String16 *pString, int w)
406 {
407   fc_assert_ret_val(pString != NULL, NULL);
408 
409   SDL_Surface *pText = create_text_surf_from_str16(pString);
410 
411   if(pText && pText->w > w - 4) {
412     /* cut string length to w length by replacing space " " with new line "\n" */
413     int ptsize = pString->ptsize;
414     Uint16 pNew_Line[2], pSpace[2];
415     Uint16 *ptr = pString->text;
416 
417     convertcopy_to_utf16(pNew_Line, sizeof(pNew_Line), "\n");
418     convertcopy_to_utf16(pSpace, sizeof(pSpace), " ");
419 
420     do {
421       if (*ptr) {
422 	if(*ptr == *pSpace) {
423           *ptr = *pNew_Line;/* "\n" */
424 	  FREESURFACE(pText);
425           pText = create_text_surf_from_str16(pString);
426 	}
427 	ptr++;
428       } else {
429 	FREESURFACE(pText);
430         if (pString->ptsize > 8) {
431 	  change_ptsize16(pString, pString->ptsize - 1);
432 	  pText = create_text_surf_from_str16(pString);
433 	} else {
434           fc_assert_ret_val(pText != NULL, NULL);
435 	}
436       }
437     } while (pText->w > w - 4);
438 
439     if(pString->ptsize != ptsize) {
440       change_ptsize16(pString, ptsize);
441     }
442   }
443 
444   return pText;
445 }
446 #endif /* 0 */
447 
448 /**************************************************************************
449   Wrap text to make it fit to given screen width.
450 **************************************************************************/
convert_string_to_const_surface_width(SDL_String16 * pString,int width)451 bool convert_string_to_const_surface_width(SDL_String16 *pString,
452                                            int width)
453 {
454   int w;
455   bool converted = FALSE;
456 
457   fc_assert_ret_val(pString != NULL, FALSE);
458   fc_assert_ret_val(pString->text != NULL, FALSE);
459 
460   w = str16size(pString).w;
461   if(w > width) {
462     /* cut string length to w length by replacing space " " with new line "\n" */
463     bool resize = FALSE;
464     int len = 0, adv;
465     Uint16 New_Line, Space;
466     Uint16 *ptr_rev, *ptr = pString->text;
467 
468     {
469       Uint16 pBuf[2];
470       convertcopy_to_utf16(pBuf, sizeof(pBuf), "\n");
471       New_Line = pBuf[0];
472       convertcopy_to_utf16(pBuf, sizeof(pBuf), " ");
473       Space = pBuf[0];
474     }
475 
476     converted = TRUE;
477 
478     do {
479       if (!resize) {
480 
481 	if (*ptr == '\0') {
482 	  resize = TRUE;
483 	  continue;
484 	}
485 
486 	if (*ptr == New_Line) {
487 	  len = 0;
488 	  ptr++;
489 	  continue;
490 	}
491 
492 	if (!((pString->style & 0x0F) & TTF_STYLE_NORMAL)) {
493     	  TTF_SetFontStyle(pString->font, (pString->style & 0x0F));
494 	}
495 	TTF_GlyphMetrics(pString->font, *ptr, NULL, NULL, NULL, NULL, &adv);
496 	if (!((pString->style & 0x0F) & TTF_STYLE_NORMAL)) {
497     	  TTF_SetFontStyle(pString->font, TTF_STYLE_NORMAL);
498 	}
499 
500 	len += adv;
501 
502 	if (len > width) {
503 	  ptr_rev = ptr;
504 	  while(ptr_rev != pString->text) {
505 	    if(*ptr_rev == Space) {
506               *ptr_rev = New_Line;/* "\n" */
507               w = str16size(pString).w;
508 	      len = 0;
509 	      break;
510 	    }
511 	    if(*ptr_rev == New_Line) {
512 	      resize = TRUE;
513 	      break;
514 	    }
515 	    ptr_rev--;
516 	  }
517 	  if (ptr_rev == pString->text) {
518 	    resize = TRUE;
519 	  }
520 	}
521 
522 	ptr++;
523       } else {
524         if (pString->ptsize > 8) {
525 	  change_ptsize16(pString, pString->ptsize - 1);
526 	  w = str16size(pString).w;
527 	} else {
528           log_error("Can't convert string to const width");
529           break;
530 	}
531       }
532 
533     } while (w > width);
534   }
535 
536   return converted;
537 }
538 
539 /**************************************************************************
540   Create surface with text drawn to it. Wrap text as needed to make it
541   fit in given width.
542 **************************************************************************/
create_text_surf_smaller_that_w(SDL_String16 * pString,int w)543 SDL_Surface * create_text_surf_smaller_that_w(SDL_String16 *pString, int w)
544 {
545   int ptsize;
546   SDL_Surface *pText;
547 
548   fc_assert_ret_val(pString != NULL, NULL);
549 
550   ptsize = pString->ptsize;
551   convert_string_to_const_surface_width(pString, w);
552   pText = create_text_surf_from_str16(pString);
553   if(pString->ptsize != ptsize) {
554     change_ptsize16(pString, ptsize);
555   }
556 
557   return pText;
558 }
559 
560 /**************************************************************************
561   Change font size of text.
562 **************************************************************************/
change_ptsize16(SDL_String16 * pString,Uint16 new_ptsize)563 void change_ptsize16(SDL_String16 *pString, Uint16 new_ptsize)
564 {
565   TTF_Font *pBuf;
566 
567   if (pString->ptsize == new_ptsize) {
568     return;
569   }
570 
571   if ((pBuf = load_font(new_ptsize)) == NULL) {
572     log_error("change_ptsize: load_font failed");
573     return;
574   }
575 
576   unload_font(pString->ptsize);
577   pString->ptsize = new_ptsize;
578   pString->font = pBuf;
579 }
580 
581 /* =================================================== */
582 
583 /**************************************************************************
584   Load font of given pointsize.
585 **************************************************************************/
load_font(Uint16 ptsize)586 static TTF_Font *load_font(Uint16 ptsize)
587 {
588   struct TTF_Font_Chain *Font_TAB_TMP = Font_TAB;
589   TTF_Font *font_tmp = NULL;
590 
591   /* find existing font and return pointer to it */
592   if (Sizeof_Font_TAB) {
593     while (Font_TAB_TMP) {
594       if (Font_TAB_TMP->ptsize == ptsize) {
595 	Font_TAB_TMP->count++;
596 	return Font_TAB_TMP->font;
597       }
598       Font_TAB_TMP = Font_TAB_TMP->next;
599     }
600   }
601 
602   if(!pFont_with_FullPath) {
603     const char *path = theme_font_filename(theme);
604     pFont_with_FullPath = fc_strdup(path);
605     fc_assert_ret_val(pFont_with_FullPath != NULL, NULL);
606   }
607 
608   /* Load Font */
609   if ((font_tmp = TTF_OpenFont(pFont_with_FullPath, ptsize)) == NULL) {
610     log_error("load_font: Couldn't load %d pt font from %s: %s",
611               ptsize, pFont_with_FullPath, SDL_GetError());
612     return font_tmp;
613   }
614 
615   /* add new font to list */
616   if (Sizeof_Font_TAB == 0) {
617     Sizeof_Font_TAB++;
618     Font_TAB_TMP = fc_calloc(1, sizeof(struct TTF_Font_Chain));
619     Font_TAB = Font_TAB_TMP;
620   } else {
621     /* Go to end of chain */
622     Font_TAB_TMP = Font_TAB;
623     while (Font_TAB_TMP->next)
624       Font_TAB_TMP = Font_TAB_TMP->next;
625 
626     Sizeof_Font_TAB++;
627     Font_TAB_TMP->next = fc_calloc(1, sizeof(struct TTF_Font_Chain));
628     Font_TAB_TMP = Font_TAB_TMP->next;
629   }
630 
631   Font_TAB_TMP->ptsize = ptsize;
632   Font_TAB_TMP->count = 1;
633   Font_TAB_TMP->font = font_tmp;
634   Font_TAB_TMP->next = NULL;
635 
636   return font_tmp;
637 }
638 
639 /**************************************************************************
640   Free font of given pointsize.
641 **************************************************************************/
unload_font(Uint16 ptsize)642 void unload_font(Uint16 ptsize)
643 {
644   int index;
645 
646   struct TTF_Font_Chain *Font_TAB_PREV = NULL;
647   struct TTF_Font_Chain *Font_TAB_TMP = Font_TAB;
648 
649   if (Sizeof_Font_TAB == 0) {
650     log_error("unload_font: Trying unload from empty Font ARRAY");
651     return;
652   }
653 
654   for (index = 0; index < Sizeof_Font_TAB; index++) {
655     if (Font_TAB_TMP->ptsize == ptsize) {
656       break;
657     }
658     Font_TAB_PREV = Font_TAB_TMP;
659     Font_TAB_TMP = Font_TAB_TMP->next;
660   }
661 
662   if (index == Sizeof_Font_TAB) {
663     log_error("unload_font: Trying unload Font which is "
664               "not included in Font ARRAY");
665     return;
666   }
667 
668   Font_TAB_TMP->count--;
669 
670   if (Font_TAB_TMP->count) {
671     return;
672   }
673 
674   TTF_CloseFont(Font_TAB_TMP->font);
675   Font_TAB_TMP->font = NULL;
676   if (Font_TAB_TMP == Font_TAB) {
677     Font_TAB = Font_TAB_TMP->next;
678   } else {
679     Font_TAB_PREV->next = Font_TAB_TMP->next;
680   }
681   Font_TAB_TMP->next = NULL;
682   Sizeof_Font_TAB--;
683   FC_FREE(Font_TAB_TMP);
684 }
685 
686 /**************************************************************************
687   Free all fonts.
688 **************************************************************************/
free_font_system(void)689 void free_font_system(void)
690 {
691   struct TTF_Font_Chain *Font_TAB_TMP;
692 
693   FC_FREE(pFont_with_FullPath);
694   while(Font_TAB) {
695     if (Font_TAB->next) {
696       Font_TAB_TMP = Font_TAB;
697       Font_TAB = Font_TAB->next;
698       if(Font_TAB_TMP->font) {
699 	TTF_CloseFont(Font_TAB_TMP->font);
700       }
701       FC_FREE(Font_TAB_TMP);
702     } else {
703       if(Font_TAB->font) {
704 	TTF_CloseFont(Font_TAB->font);
705       }
706       FC_FREE(Font_TAB);
707     }
708   }
709 }
710