1 /*
2 ** v_text.cpp
3 ** Draws text to a canvas. Also has a text line-breaker thingy.
4 **
5 **---------------------------------------------------------------------------
6 ** Copyright 1998-2006 Randy Heit
7 ** All rights reserved.
8 **
9 ** Redistribution and use in source and binary forms, with or without
10 ** modification, are permitted provided that the following conditions
11 ** are met:
12 **
13 ** 1. Redistributions of source code must retain the above copyright
14 ** notice, this list of conditions and the following disclaimer.
15 ** 2. Redistributions in binary form must reproduce the above copyright
16 ** notice, this list of conditions and the following disclaimer in the
17 ** documentation and/or other materials provided with the distribution.
18 ** 3. The name of the author may not be used to endorse or promote products
19 ** derived from this software without specific prior written permission.
20 **
21 ** THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22 ** IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23 ** OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24 ** IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25 ** INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26 ** NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27 ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28 ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29 ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30 ** THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 **---------------------------------------------------------------------------
32 **
33 */
34
35 #include <stdlib.h>
36 #include <stdarg.h>
37 #include <ctype.h>
38
39 #include "v_text.h"
40
41 #include "i_system.h"
42 #include "v_video.h"
43 #include "hu_stuff.h"
44 #include "w_wad.h"
45 #include "m_swap.h"
46
47 #include "doomstat.h"
48 #include "templates.h"
49
50 //
51 // DrawChar
52 //
53 // Write a single character using the given font
54 //
DrawChar(FFont * font,int normalcolor,int x,int y,BYTE character,...)55 void STACK_ARGS DCanvas::DrawChar (FFont *font, int normalcolor, int x, int y, BYTE character, ...)
56 {
57 if (font == NULL)
58 return;
59
60 if (normalcolor >= NumTextColors)
61 normalcolor = CR_UNTRANSLATED;
62
63 FTexture *pic;
64 int dummy;
65
66 if (NULL != (pic = font->GetChar (character, &dummy)))
67 {
68 const FRemapTable *range = font->GetColorTranslation ((EColorRange)normalcolor);
69 va_list taglist;
70 va_start (taglist, character);
71 DrawTexture (pic, x, y, DTA_Translation, range, TAG_MORE, &taglist);
72 va_end (taglist);
73 }
74 }
75
76 //
77 // DrawText
78 //
79 // Write a string using the given font
80 //
DrawTextV(FFont * font,int normalcolor,int x,int y,const char * string,va_list taglist)81 void DCanvas::DrawTextV(FFont *font, int normalcolor, int x, int y, const char *string, va_list taglist)
82 {
83 INTBOOL boolval;
84 va_list tags;
85 uint32 tag;
86
87 int maxstrlen = INT_MAX;
88 int w, maxwidth;
89 const BYTE *ch;
90 int c;
91 int cx;
92 int cy;
93 int boldcolor;
94 const FRemapTable *range;
95 int height;
96 int forcedwidth = 0;
97 int scalex, scaley;
98 int kerning;
99 FTexture *pic;
100
101 if (font == NULL || string == NULL)
102 return;
103
104 if (normalcolor >= NumTextColors)
105 normalcolor = CR_UNTRANSLATED;
106 boldcolor = normalcolor ? normalcolor - 1 : NumTextColors - 1;
107
108 range = font->GetColorTranslation ((EColorRange)normalcolor);
109 height = font->GetHeight () + 1;
110 kerning = font->GetDefaultKerning ();
111
112 ch = (const BYTE *)string;
113 cx = x;
114 cy = y;
115
116 // Parse the tag list to see if we need to adjust for scaling.
117 maxwidth = Width;
118 scalex = scaley = 1;
119
120 #ifndef NO_VA_COPY
121 va_copy(tags, taglist);
122 #else
123 tags = taglist;
124 #endif
125 tag = va_arg(tags, uint32);
126
127 while (tag != TAG_DONE)
128 {
129 va_list *more_p;
130 DWORD data;
131
132 switch (tag)
133 {
134 case TAG_IGNORE:
135 default:
136 data = va_arg (tags, DWORD);
137 break;
138
139 case TAG_MORE:
140 more_p = va_arg (tags, va_list*);
141 va_end (tags);
142 #ifndef NO_VA_COPY
143 va_copy (tags, *more_p);
144 #else
145 tags = *more_p;
146 #endif
147 break;
148
149 // We don't handle these. :(
150 case DTA_DestWidth:
151 case DTA_DestHeight:
152 case DTA_Translation:
153 assert("Bad parameter for DrawText" && false);
154 return;
155
156 case DTA_CleanNoMove_1:
157 boolval = va_arg (tags, INTBOOL);
158 if (boolval)
159 {
160 scalex = CleanXfac_1;
161 scaley = CleanYfac_1;
162 maxwidth = Width - (Width % scalex);
163 }
164 break;
165
166 case DTA_CleanNoMove:
167 boolval = va_arg (tags, INTBOOL);
168 if (boolval)
169 {
170 scalex = CleanXfac;
171 scaley = CleanYfac;
172 maxwidth = Width - (Width % scalex);
173 }
174 break;
175
176 case DTA_Clean:
177 case DTA_320x200:
178 boolval = va_arg (tags, INTBOOL);
179 if (boolval)
180 {
181 scalex = scaley = 1;
182 maxwidth = 320;
183 }
184 break;
185
186 case DTA_VirtualWidth:
187 maxwidth = va_arg (tags, int);
188 scalex = scaley = 1;
189 break;
190
191 case DTA_TextLen:
192 maxstrlen = va_arg (tags, int);
193 break;
194
195 case DTA_CellX:
196 forcedwidth = va_arg (tags, int);
197 break;
198
199 case DTA_CellY:
200 height = va_arg (tags, int);
201 break;
202 }
203 tag = va_arg (tags, uint32);
204 }
205 va_end(tags);
206
207 height *= scaley;
208
209 while ((const char *)ch - string < maxstrlen)
210 {
211 c = *ch++;
212 if (!c)
213 break;
214
215 if (c == TEXTCOLOR_ESCAPE)
216 {
217 EColorRange newcolor = V_ParseFontColor (ch, normalcolor, boldcolor);
218 if (newcolor != CR_UNDEFINED)
219 {
220 range = font->GetColorTranslation (newcolor);
221 }
222 continue;
223 }
224
225 if (c == '\n')
226 {
227 cx = x;
228 cy += height;
229 continue;
230 }
231
232 if (NULL != (pic = font->GetChar (c, &w)))
233 {
234 #ifndef NO_VA_COPY
235 va_copy(tags, taglist);
236 #else
237 tags = taglist;
238 #endif
239 if (forcedwidth)
240 {
241 w = forcedwidth;
242 DrawTexture (pic, cx, cy,
243 DTA_Translation, range,
244 DTA_DestWidth, forcedwidth,
245 DTA_DestHeight, height,
246 TAG_MORE, &tags);
247 }
248 else
249 {
250 DrawTexture (pic, cx, cy,
251 DTA_Translation, range,
252 TAG_MORE, &tags);
253 }
254 va_end (tags);
255 }
256 cx += (w + kerning) * scalex;
257 }
258 va_end(taglist);
259 }
260
DrawText(FFont * font,int normalcolor,int x,int y,const char * string,...)261 void STACK_ARGS DCanvas::DrawText (FFont *font, int normalcolor, int x, int y, const char *string, ...)
262 {
263 va_list tags;
264 va_start(tags, string);
265 DrawTextV(font, normalcolor, x, y, string, tags);
266 }
267
268 // A synonym so that this can still be used in files that #include Windows headers
DrawTextA(FFont * font,int normalcolor,int x,int y,const char * string,...)269 void STACK_ARGS DCanvas::DrawTextA (FFont *font, int normalcolor, int x, int y, const char *string, ...)
270 {
271 va_list tags;
272 va_start(tags, string);
273 DrawTextV(font, normalcolor, x, y, string, tags);
274 }
275
276 //
277 // Find string width using this font
278 //
StringWidth(const BYTE * string) const279 int FFont::StringWidth (const BYTE *string) const
280 {
281 int w = 0;
282 int maxw = 0;
283
284 while (*string)
285 {
286 if (*string == TEXTCOLOR_ESCAPE)
287 {
288 ++string;
289 if (*string == '[')
290 {
291 while (*string != '\0' && *string != ']')
292 {
293 ++string;
294 }
295 }
296 if (*string != '\0')
297 {
298 ++string;
299 }
300 continue;
301 }
302 else if (*string == '\n')
303 {
304 if (w > maxw)
305 maxw = w;
306 w = 0;
307 ++string;
308 }
309 else
310 {
311 w += GetCharWidth (*string++) + GlobalKerning;
312 }
313 }
314
315 return MAX (maxw, w);
316 }
317
318 //
319 // Break long lines of text into multiple lines no longer than maxwidth pixels
320 //
breakit(FBrokenLines * line,FFont * font,const BYTE * start,const BYTE * stop,FString & linecolor)321 static void breakit (FBrokenLines *line, FFont *font, const BYTE *start, const BYTE *stop, FString &linecolor)
322 {
323 if (!linecolor.IsEmpty())
324 {
325 line->Text = TEXTCOLOR_ESCAPE;
326 line->Text += linecolor;
327 }
328 line->Text.AppendCStrPart ((const char *)start, stop - start);
329 line->Width = font->StringWidth (line->Text);
330 }
331
V_BreakLines(FFont * font,int maxwidth,const BYTE * string,bool preservecolor)332 FBrokenLines *V_BreakLines (FFont *font, int maxwidth, const BYTE *string, bool preservecolor)
333 {
334 FBrokenLines lines[128]; // Support up to 128 lines (should be plenty)
335
336 const BYTE *space = NULL, *start = string;
337 size_t i, ii;
338 int c, w, nw;
339 FString lastcolor, linecolor;
340 bool lastWasSpace = false;
341 int kerning = font->GetDefaultKerning ();
342
343 i = w = 0;
344
345 while ( (c = *string++) && i < countof(lines) )
346 {
347 if (c == TEXTCOLOR_ESCAPE)
348 {
349 if (*string)
350 {
351 if (*string == '[')
352 {
353 const BYTE *start = string;
354 while (*string != ']' && *string != '\0')
355 {
356 string++;
357 }
358 if (*string != '\0')
359 {
360 string++;
361 }
362 lastcolor = FString((const char *)start, string - start);
363 }
364 else
365 {
366 lastcolor = *string++;
367 }
368 }
369 continue;
370 }
371
372 if (isspace(c))
373 {
374 if (!lastWasSpace)
375 {
376 space = string - 1;
377 lastWasSpace = true;
378 }
379 }
380 else
381 {
382 lastWasSpace = false;
383 }
384
385 nw = font->GetCharWidth (c);
386
387 if ((w > 0 && w + nw > maxwidth) || c == '\n')
388 { // Time to break the line
389 if (!space)
390 space = string - 1;
391
392 breakit (&lines[i], font, start, space, linecolor);
393 if (c == '\n' && !preservecolor)
394 {
395 lastcolor = ""; // Why, oh why, did I do it like this?
396 }
397 linecolor = lastcolor;
398
399 i++;
400 w = 0;
401 lastWasSpace = false;
402 start = space;
403 space = NULL;
404
405 while (*start && isspace (*start) && *start != '\n')
406 start++;
407 if (*start == '\n')
408 start++;
409 else
410 while (*start && isspace (*start))
411 start++;
412 string = start;
413 }
414 else
415 {
416 w += nw + kerning;
417 }
418 }
419
420 // String here is pointing one character after the '\0'
421 if (i < countof(lines) && --string - start >= 1)
422 {
423 const BYTE *s = start;
424
425 while (s < string)
426 {
427 // If there is any non-white space in the remainder of the string, add it.
428 if (!isspace (*s++))
429 {
430 breakit (&lines[i++], font, start, string, linecolor);
431 break;
432 }
433 }
434 }
435
436 // Make a copy of the broken lines and return them
437 FBrokenLines *broken = new FBrokenLines[i+1];
438
439 for (ii = 0; ii < i; ++ii)
440 {
441 broken[ii] = lines[ii];
442 }
443 broken[ii].Width = -1;
444
445 return broken;
446 }
447
V_FreeBrokenLines(FBrokenLines * lines)448 void V_FreeBrokenLines (FBrokenLines *lines)
449 {
450 if (lines)
451 {
452 delete[] lines;
453 }
454 }
455