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