1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  *
22  * Based on the original sources
23  *   Faery Tale II -- The Halls of the Dead
24  *   (c) 1993-1996 The Wyrmkeep Entertainment Co.
25  */
26 
27 #include "saga2/saga2.h"
28 #include "saga2/gdraw.h"
29 
30 namespace Saga2 {
31 
32 #define textStyleBar    (textStyleUnderBar|textStyleHiLiteBar)
33 
34 #define TempAlloc       malloc
35 #define TempFree        free
36 
37 /* ============================================================================ *
38                             Text Blitting Routines
39  * ============================================================================ */
40 
41 /*  Notes:
42 
43     These functions attempt to render planar fonts onto a chunky bitmap.
44 
45     All non-pointer variables are 16 bits or less. (I don't know how
46     big pointers need to be).
47 */
48 
49 /*  DrawChar: This function renders a single bitmapped character
50     into an offscreen buffer.
51 
52     'drawchar' is the ascii code of the character to be drawn.
53 
54     'xpos' is the position in the buffer to draw the outline.
55 
56     'baseline' is the address of the first scanline in the buffer.
57     (it can actually be any scanline in the buffer if we want to
58     draw the character at a different y position).
59     Note that this has nothing to do with the Amiga "BASELINE" field,
60     this routine always renders relative to the top of the font.
61 
62     'color' is the pixel value to render into the buffer.
63 
64     Note that this code does unaligned int16 writes to byte addresses,
65     and will not run on an 68000 or 68010 processor.
66 */
67 
DrawChar(gFont * font,int drawchar,int xpos,uint8 * baseline,uint8 color,uint16 destwidth)68 void DrawChar(gFont *font, int drawchar, int xpos, uint8 *baseline, uint8 color,
69               uint16 destwidth) {
70 	short   w,
71 	        font_mod;               // width of font in bytes
72 
73 	uint8   *src,                   // start of char data
74 	        *dst;                   // start of dest row
75 
76 	/*
77 	    This code assumes that the font characters are all byte-aligned,
78 	    and that there is no extra junk after the character in the bits
79 	    that pad to the next byte. Also, the code currently makes no
80 	    provision for "LoChar" or "HiChar" (i.e. there are blank table
81 	    entries for characters 0-1f). This can be changed if desired.
82 	*/
83 
84 	font_mod = font->rowMod;
85 	uint16 offset = font->charXOffset[drawchar];
86 	src = &font->fontdata[offset];
87 	dst = baseline + xpos;
88 
89 	for (w = font->charWidth[drawchar]; w > 0; w -= 8) {
90 		uint8   *src_row,
91 		        *dst_row;
92 		short   h;
93 
94 		src_row = src;
95 		dst_row = dst;
96 
97 		for (h = font->height; h > 0; h--) {
98 			uint8   *b;
99 			uint8   s;
100 
101 			b = dst_row;
102 
103 			for (s = *src_row; s != 0; s <<= 1) {
104 				if ((s & 0x80) != 0)
105 					*b = color;
106 				++b;
107 			}
108 			src_row += font_mod;
109 			dst_row += destwidth;
110 		}
111 
112 		++src;
113 		dst += 8;
114 	}
115 }
116 /*  DrawChar3x3Outline:
117 
118     This function renders a single bitmapped character into an offscreen
119     buffer. The character will be "ballooned", i.e. expanded, by 1 pixel
120     in each direction. It does not render the center part of the outlined
121     character in a different color -- that must be done as a seperate
122     step.
123 
124     'drawchar' is the ascii code of the character to be drawn.
125 
126     'xpos' is the position in the buffer to draw the outline. Note that
127     the first pixel of the outline may be drawn at xpos-1, since the
128     outline expands in both directions.
129 
130     'baseline' is the address of the first scanline in the buffer.
131     (it can actually be any scanline in the buffer if we want to
132     draw the character at a different y position).
133     Note that this has nothing to do with the Amiga "BASELINE" field,
134     this routine always renders relative to the top of the font.
135 
136     'color' is the pixel value to render into the buffer.
137 
138     This code assumes that the font characters are all byte-aligned,
139     and that there is no extra junk after the character in the bits
140     that pad to the next byte. Also, the code currently makes no
141     provision for "LoChar" or "HiChar" (i.e. there are blank table
142     entries for characters 0-1f). This can be changed if desired.
143 
144     Note that this code does unaligned int16 writes to byte addresses,
145     and will not run on an 68000 or 68010 processor. (Even with the
146     speed penalty, this is still pretty fast).
147 */
148 
149 #define SQUARE_OUTLINES 1
150 
DrawChar3x3Outline(gFont * font,int drawchar,int xpos,uint8 * baseline,uint8 color,uint16 destwidth)151 void DrawChar3x3Outline(gFont *font, int drawchar, int xpos, uint8 *baseline,
152                         uint8 color, uint16 destwidth) {
153 	uint8           *d;
154 	short           h,              // font height counter
155 	                rowmod;
156 
157 	short           charwidth, w;   // width of character in pixels
158 
159 	uint8           *chardata;      // pointer to start of char data
160 
161 	uint8           *src, *dst;
162 	unsigned short  s;
163 	unsigned short  txt1, txt2, txt3;
164 
165 	//  point to the first byte of the first scanline of the source char
166 	uint16 offset = font->charXOffset[drawchar];
167 	chardata = &font->fontdata[offset];
168 
169 	//  get the width of the character in pixels
170 	charwidth = font->charWidth[drawchar];
171 
172 	//  point to the first byte of where we want to place the character
173 	baseline += xpos - 1;
174 
175 	//  this loop goes once for each 8 pixels wide that the character is
176 	rowmod = font->rowMod;
177 
178 	for (w = (charwidth + 7) >> 3; w > 0; w--) {
179 		src = chardata;
180 		dst = baseline;
181 
182 		txt1 = txt2 = 0;
183 
184 		for (h = font->height + 2; h; h--) {
185 			d = dst;
186 
187 			txt3 = txt2;
188 			txt2 = txt1;
189 			txt1 = h > 2 ? *src : 0;
190 
191 			s = txt1 | txt2 | txt3;
192 
193 			s = s | (s << 1) | (s << 2);
194 
195 			while (s) {
196 				if (s & 0x200)
197 					*d = color;
198 				++d;
199 				s <<= 1;
200 			}
201 
202 			src += rowmod;
203 			dst += destwidth;
204 		}
205 
206 		chardata++;
207 		baseline += 8;
208 	}
209 
210 }
211 
DrawChar5x5Outline(gFont * font,int drawchar,int xpos,uint8 * baseline,uint8 color,uint16 destwidth)212 void DrawChar5x5Outline(gFont *font, int drawchar, int xpos, uint8 *baseline,
213                         uint8 color, uint16 destwidth) {
214 	uint8           *d;
215 	short           h,              /* font height counter              */
216 	                rowmod;
217 
218 	short           charwidth, w;   /* width of character in pixels     */
219 
220 	uint8           *chardata;      /* pointer to start of char data    */
221 
222 	uint8           *src, *dst;
223 	unsigned short  s0, s1;
224 	unsigned short  txt[5];
225 
226 	//  point to the first byte of the first scanline of the source char
227 	uint16 offset = font->charXOffset[drawchar];
228 	chardata = &font->fontdata[offset];
229 
230 	//  get the width of the character in pixels
231 	charwidth = font->charWidth[drawchar];
232 
233 	//  point to the first byte of where we want to place the character
234 	baseline += xpos - 2;
235 
236 	//  this loop goes once for each 8 pixels wide that the character is
237 	rowmod = font->rowMod;
238 
239 	for (w = (charwidth + 7) >> 3; w > 0; w--) {
240 		src = chardata;
241 		dst = baseline;
242 
243 		txt[0] = txt[1] = txt[2] = txt[3 ] = txt[ 4 ] = 0;
244 
245 		for (h = font->height + 4; h; h--) {
246 			d = dst;
247 
248 			txt[4] = txt[3];
249 			txt[3] = txt[2];
250 			txt[2] = txt[1];
251 			txt[1] = txt[0];
252 			txt[0] = h > 4 ? *src : 0;
253 
254 			s0 = txt[1] | txt[2] | txt[3];
255 			s1 = s0 | txt[0] | txt[4];
256 
257 			s0 = s0 | (s0 << 1) | (s0 << 2) | (s0 << 3) | (s0 << 4);
258 			s0 |= (s1 << 1) | (s1 << 2) | (s1 << 3);
259 
260 			while (s0) {
261 				if (s0 & 0x800)
262 					*d = color;
263 				++d;
264 				s0 <<= 1;
265 			}
266 
267 			src += rowmod;
268 			dst += destwidth;
269 		}
270 
271 		chardata++;
272 		baseline += 8;
273 	}
274 
275 }
276 
277 /*  A private routine to render a string of characters into a temp
278     buffer.
279 */
280 
drawStringChars(const char * str,int16 len,gPixelMap & dest,int xpos,int ypos)281 void gPort::drawStringChars(
282     const char      *str,                   // string to draw
283     int16           len,                    // length of string
284     gPixelMap       &dest,
285     int             xpos,                   // x position to draw it
286     int             ypos) {                 // y position to draw it
287 	const char     *s;                     // pointer to string
288 	uint8           drawchar;               // char to draw
289 	int16           x;                      // current x position
290 	uint8           *buffer,                // buffer to render to
291 	                *uBuffer;               // underline buffer
292 	uint16          drowMod = dest.size.x;  // row modulus of dest
293 	int16           i;                      // loop index
294 	uint8           underbar = (textStyles & textStyleBar) != 0;
295 	bool            underscore;
296 	int16           underPos;
297 
298 	// the address to start rendering pixels to.
299 
300 	underPos = font->baseLine + 2;
301 	if (underPos > font->height) underPos = font->height;
302 	buffer = dest.data + (ypos * drowMod);
303 	uBuffer = buffer + (underPos * drowMod);
304 
305 	// draw drop-shadow, if any
306 
307 	if (textStyles & textStyleShadow) {
308 		x = xpos - 1;
309 		s = str;
310 
311 		if (textStyles & textStyleOutline) { // if outlining
312 			for (i = 0; i < len; i++) {
313 				drawchar = *s++;            // draw thick drop shadow
314 				x += font->charKern[drawchar];
315 				DrawChar3x3Outline(font, drawchar, x, buffer, shPen, drowMod);
316 				x += font->charSpace[drawchar] + textSpacing;
317 			}
318 		} else if (textStyles & textStyleThickOutline) { // if outlining
319 			for (i = 0; i < len; i++) {
320 				drawchar = *s++;                // draw thick drop shadow
321 				x += font->charKern[drawchar];
322 				DrawChar5x5Outline(font, drawchar, x, buffer, shPen, drowMod);
323 				x += font->charSpace[drawchar] + textSpacing;
324 			}
325 		} else {
326 			for (i = 0; i < len; i++) {
327 				drawchar = *s++;            // draw thick drop shadow
328 				x += font->charKern[drawchar];
329 				DrawChar(font, drawchar, x, buffer + drowMod,
330 				         shPen, drowMod);
331 				x += font->charSpace[drawchar] + textSpacing;
332 			}
333 		}
334 	}
335 
336 	// draw outline, if any
337 
338 	if (textStyles & textStyleOutline) { // if outlining
339 		x = xpos;
340 		s = str;
341 
342 		for (i = 0; i < len; i++) {
343 			drawchar = *s++;                // draw thick text
344 			x += font->charKern[drawchar];
345 			DrawChar3x3Outline(font, drawchar, x, buffer - drowMod,
346 			                   olPen, drowMod);
347 			x += font->charSpace[drawchar] + textSpacing;
348 		}
349 	} else if (textStyles & textStyleThickOutline) { // if thick outlining
350 		x = xpos;
351 		s = str;
352 
353 		for (i = 0; i < len; i++) {
354 			drawchar = *s++;                // draw extra thick text
355 			x += font->charKern[drawchar];
356 			DrawChar5x5Outline(font, drawchar, x, buffer - drowMod * 2,
357 			                   olPen, drowMod);
358 			x += font->charSpace[drawchar] + textSpacing;
359 		}
360 	}
361 
362 	// draw inner part
363 
364 	x = xpos;
365 	s = str;
366 	underscore = textStyles & textStyleUnderScore ? true : false;
367 
368 	for (i = 0; i < len; i++) {
369 		int16       last_x = x;
370 		uint8       color = fgPen;
371 
372 		drawchar = *s++;                // draw thick drop shadow
373 		if (drawchar == '_' && underbar) {
374 			len--;
375 			drawchar = *s++;
376 			if (textStyles & textStyleUnderBar)
377 				underscore = true;
378 			if (textStyles & textStyleHiLiteBar)
379 				color = bgPen;
380 		}
381 		x += font->charKern[drawchar];
382 		DrawChar(font, drawchar, x, buffer, color, drowMod);
383 		x += font->charSpace[drawchar] + textSpacing;
384 
385 		if (underscore) {               // draw underscore
386 			uint8   *put = uBuffer + last_x;
387 			int16   width = x - last_x;
388 
389 			while (width-- > 0) {
390 				*put++ = color;
391 			}
392 
393 			if (!(textStyles & textStyleUnderScore))
394 				underscore = false;
395 		}
396 	}
397 }
398 
399 //  Draws a string clipped to the current clipping rectangle.
400 //  Note that if several clipping rectangles were to be used,
401 //  we would probably do this differently...
402 
drawClippedString(const char * s,int16 len,int xpos,int ypos)403 int16 gPort::drawClippedString(
404     const char      *s,                     // string to draw
405     int16           len,                    // length of string
406     int             xpos,                   // x position to draw it
407     int             ypos) {                 // y position to draw it
408 	int16           clipWidth = 0,          // width of clipped string
409 	                clipLen;                // chars to draw
410 	gPixelMap       tempMap;                // temp buffer for text
411 	uint8           underbar = (textStyles & textStyleBar) != 0;
412 	int16           xoff = 0,               // offset for outlines
413 	                yoff = 0;               // offset for outlines
414 	int16           penMove = 0;            // keep track of pen movement
415 
416 	//  First, we want to avoid rendering any characters to the
417 	//  left of the clipping rectangle. Scan the string until
418 	//  we find a character that is not completely to the left
419 	//  of the clip.
420 
421 	while (len > 0) {
422 		int16       drawchar = *s,
423 		            charwidth;
424 
425 		if (drawchar == '_' && underbar) {
426 			drawchar = s[1];
427 			charwidth   = font->charKern[drawchar]
428 			              + font->charSpace[drawchar] + textSpacing;
429 
430 			if (xpos + charwidth >= clip.x)
431 				break;
432 			s++;
433 		} else {
434 			charwidth   = font->charKern[drawchar]
435 			              + font->charSpace[drawchar] + textSpacing;
436 			if (xpos + charwidth >= clip.x)
437 				break;
438 		}
439 
440 		s++;
441 		len--;
442 		xpos += charwidth;
443 		penMove += charwidth;
444 	}
445 
446 	//  Now, we also want to only draw that portion of the string
447 	//  that actually appears in the clip, so scan the rest of the
448 	//  string until we find a character who's left edge is past
449 	//  the right edge of the clip.
450 
451 	for (clipLen = 0; clipLen < len; clipLen++) {
452 		int16       drawchar = s[clipLen];
453 
454 		if (drawchar == '_' && underbar)
455 			continue;
456 
457 		clipWidth += font->charKern[drawchar]
458 		             + font->charSpace[drawchar] + textSpacing;
459 
460 		if (xpos > clip.x + clip.width)
461 			break;
462 	}
463 
464 	//  Handle special case of negative kern value of 1st character
465 
466 	if (font->charKern[(byte)s[0]] < 0) {
467 		int16       kern = - font->charKern[(byte)s[0]];
468 
469 		clipWidth += kern;              // increase size of map to render
470 		xoff += kern;                   // offset text into map right
471 		xpos -= kern;                   // offset map into port left
472 	}
473 
474 	//  Set up a temporary bitmap to hold the string.
475 
476 	tempMap.size.x = clipWidth;
477 	tempMap.size.y = font->height;
478 
479 	//  Adjust the size and positioning of the temp map due
480 	//  to text style effects.
481 
482 	if (textStyles & textStyleOutline) {
483 		xoff = yoff = 1;
484 		xpos--;
485 		ypos--;
486 		tempMap.size.x += 2;
487 		tempMap.size.y += 2;
488 	} else if (textStyles & textStyleThickOutline) {
489 		xoff = yoff = 2;
490 		xpos -= 2;
491 		ypos -= 2;
492 		tempMap.size.x += 4;
493 		tempMap.size.y += 4;
494 	}
495 
496 	if (textStyles & (textStyleShadow | textStyleUnderScore | textStyleUnderBar)) {
497 		tempMap.size.x += 1;
498 		tempMap.size.y += 1;
499 	}
500 
501 	if (textStyles & textStyleItalics) {
502 		int n = (font->height - font->baseLine - 1) / 2;
503 
504 		if (n > 0) xpos += n;
505 		tempMap.size.x += tempMap.size.y / 2;
506 	}
507 
508 	//  Allocate a temporary bitmap
509 
510 	if (tempMap.bytes() == 0)
511 		return 0;
512 	tempMap.data = (uint8 *)TempAlloc(tempMap.bytes());
513 	if (tempMap.data != NULL) {
514 		//  Fill the buffer with background pen if we're
515 		//  not doing a transparent blit.
516 
517 		memset(tempMap.data,
518 		       (drawMode == drawModeReplace) ? bgPen : 0,
519 		       tempMap.bytes());
520 
521 		//  Draw the characters into the buffer
522 
523 		drawStringChars(s, clipLen, tempMap, xoff, yoff);
524 
525 		//  apply slant if italics
526 
527 		if (textStyles & textStyleItalics) {
528 			int n = (font->height - font->baseLine - 1) / 2;
529 			int shift = (n > 0 ? n : 0);
530 			int flag = (font->height - font->baseLine - 1) & 1;
531 
532 			shift = -shift;
533 
534 			for (int k = font->height - 1; k >= 0; k--) {
535 				if (shift < 0) {
536 					uint8   *dest = tempMap.data + k * tempMap.size.x,
537 					         *src = dest - shift;
538 					int     j;
539 
540 					for (j = 0; j < tempMap.size.x + shift; j++) {
541 						*dest++ = *src++;
542 					}
543 					for (; j < tempMap.size.x; j++) {
544 						*dest++ = 0;
545 					}
546 				} else if (shift > 0) {
547 					uint8   *dest = tempMap.data + (k + 1) * tempMap.size.x,
548 					         *src = dest - shift;
549 					int     j;
550 
551 					for (j = 0; j < tempMap.size.x - shift; j++) {
552 						*--dest = *--src;
553 					}
554 					for (; j < tempMap.size.x; j++) {
555 						*--dest = 0;
556 					}
557 				}
558 
559 				flag ^= 1;
560 				if (flag)
561 					shift++;
562 			}
563 		}
564 
565 		//  blit the temp buffer onto the screen.
566 
567 		bltPixels(tempMap, 0, 0,
568 		          xpos, ypos,
569 		          tempMap.size.x, tempMap.size.y);
570 
571 		TempFree(tempMap.data);
572 	}
573 
574 	//  Now, we still need to scan the rest of the string
575 	//  so that we can move the pen position by the right amount.
576 
577 //	penMove += clipWidth;
578 	for (clipLen = 0; clipLen < len; clipLen++) {
579 		int16       drawchar = s[clipLen];
580 
581 		if (drawchar == '_' && underbar)
582 			continue;
583 
584 		penMove += font->charKern[drawchar]
585 		           + font->charSpace[drawchar] + textSpacing;
586 	}
587 
588 	return penMove;
589 }
590 
591 /********* gtext.cpp/gPort::drawText *********************************
592 *
593 *   NAME
594 *       gPort::drawText -- draw a text string into a gPort
595 *
596 *   SYNOPSIS
597 *       port.drawText( str, length );
598 *
599 *       void gPort::drawText( char *, int16 = -1 );
600 *
601 *   FUNCTION
602 *       This function draws a string of text at the current pen
603 *       position in the current pen color. The pen position is
604 *       updated to the end of the text.
605 *
606 *       The text will be positioned such that the top-left corner
607 *       of the first character will be at the pen position.
608 *
609 *   INPUTS
610 *       str         A string of characters.
611 *
612 *       length      [Optional parameter] If supplied, it indicates
613 *                   the length of the string; Otherwise, strlen()
614 *                   is used.
615 *
616 *   RESULT
617 *       none
618 *
619 *   SEE ALSO
620 *       gPort class
621 *
622 **********************************************************************
623 */
drawText(const char * str,int16 length)624 void gPort::drawText(
625     const char      *str,                   /* string to draw               */
626     int16           length) {
627 	if (length < 0)
628 		length = strlen(str);
629 
630 	if (length > 0)
631 		penPos.x += drawClippedString(str, length, penPos.x, penPos.y);
632 }
633 
634 /********* gtext.cpp/gPort::drawTextInBox *********************************
635 *
636 *   NAME
637 *       gPort::drawTextInBox -- draw text within a box
638 *
639 *   SYNOPSIS
640 *       port.drawTextInBox( str, len, rect, pos, borderSpace );
641 *
642 *       void gPort::drawTextInBox( char *, int16, const Rect16 &,
643 *       /c/ int16, Point16 );
644 *
645 *   FUNCTION
646 *       This function can draw a text string centered or justified
647 *       within a rectangular area, and clipped to that area as well.
648 *       The text string is drawn in the current pen color, and with
649 *       the current draw mode.
650 *
651 *   INPUTS
652 *       str         The text to draw.
653 *
654 *       len         The length of the string or -1 to indicate a
655 *                   null-terminated string.
656 *
657 *       rect        The rectangle to draw the text within.
658 *
659 *       pos         How to position the text string within the
660 *                   rectangle. The default (0) is to center the
661 *                   string both horizontally and vertically; However,
662 *                   the following flags will modify this:
663 *
664 *       /i/         textPosLeft -- draw text left-justified.
665 *
666 *       /i/         textPosRight -- draw text right-justified.
667 *
668 *       /i/         textPosHigh -- draw text flush with top edge.
669 *
670 *       /i/         textPosLow -- draw text flush with bottom edge.
671 *
672 *       borderSpace A Point16 object, which indicates how much space
673 *                   (in both x and y) to place between the text and
674 *                   the border when the text is justified to a
675 *                   particular edge. Does not apply when text is centered.
676 *
677 *   RESULT
678 *       none
679 *
680 *   SEE ALSO
681 *       gPort class
682 *
683 **********************************************************************
684 */
drawTextInBox(const char * str,int16 length,const Rect16 & r,int16 pos,Point16 borders)685 void gPort::drawTextInBox(
686     const char      *str,
687     int16           length,
688     const Rect16    &r,
689     int16           pos,
690     Point16         borders) {
691 	int16           height, width;
692 	int16           x, y;
693 	Rect16          newClip,
694 	                saveClip = clip;
695 
696 	if (!font)
697 		return;
698 
699 	height = font->height;
700 	width  = TextWidth(font, str, length, textStyles);
701 
702 	if (textStyles & (textStyleUnderScore | textStyleUnderBar)) {
703 		if (font->baseLine + 2 >= font->height)
704 			height++;
705 	}
706 
707 	//  Calculate x position of text string
708 
709 	if (pos & textPosLeft)
710 		x = r.x + borders.x;
711 	else if (pos & textPosRight)
712 		x = r.x + r.width - width - borders.x;
713 	else
714 		x = r.x + (r.width - width) / 2;
715 
716 	//  Calculate y position of text string
717 
718 	if (pos & textPosHigh)
719 		y = r.y + borders.y;
720 	else if (pos & textPosLow)
721 		y = r.y + r.height - height - borders.y;
722 	else
723 		y = r.y + (r.height - height) / 2;
724 
725 	//  Calculate clipping region
726 
727 	clip = intersect(clip, r);
728 
729 	//  Draw the text
730 
731 	moveTo(x, y);
732 	drawText(str, length);
733 
734 	//  Restore the clipping region
735 
736 	clip = saveClip;
737 }
738 
739 //  Attach to gFont?
740 
741 /********* gtext.cpp/TextWidth ***************************************
742 *
743 *   NAME
744 *       TextWidth -- returns length of text string in pixels
745 *
746 *   SYNOPSIS
747 *       width = TextWidth( font, str, len, styles );
748 *
749 *       int16 TextWidth( gFont *, char *, int16, int16 );
750 *
751 *   FUNCTION
752 *       This function computes the length of a text string in pixels
753 *       for a given font and text style.
754 *
755 *   INPUTS
756 *       font        The text font of the text.
757 *
758 *       str         The text string to measure.
759 *
760 *       len         The length of the string, or -1 to use strlen()
761 *
762 *       styles      The text rendering style (see gPort::setStyle).
763 *                   0 = normal.
764 *
765 *   RESULT
766 *       The width of the text.
767 *
768 **********************************************************************
769 */
TextWidth(gFont * font,const char * s,int16 length,int16 styles)770 int16 TextWidth(gFont *font, const char *s, int16 length, int16 styles) {
771 	int16           count = 0;
772 
773 	if (length < 0)
774 		length = strlen(s);
775 
776 	while (length--) {
777 		uint8       chr = *s++;
778 
779 		if (chr == '_' && (styles & textStyleBar))
780 			continue;
781 
782 		count += font->charKern[chr] + font->charSpace[chr];
783 	}
784 
785 	if (styles & textStyleItalics) {
786 		count += (font->baseLine + 1) / 2 +
787 		         (font->height - font->baseLine - 1) / 2;
788 	}
789 	if (styles & textStyleOutline)
790 		count += 2;
791 	else if (styles & textStyleThickOutline)
792 		count += 4;
793 	if (styles & textStyleShadow)
794 		count += 1;
795 
796 	return count;
797 }
798 
799 //  Searches for the insertion point between chars under the cursor
800 
801 /********* gtext.cpp/WhichIChar **************************************
802 *
803 *   NAME
804 *       WhichIChar -- search for insertion point between characters
805 *
806 *   SYNOPSIS
807 *       charNum = WhichIChar( font, str, pick, maxLen );
808 *
809 *       int16 WhichIChar( gFont *, uint8 *, int16, int16 );
810 *
811 *   FUNCTION
812 *       This function is used by the gTextBox class to select
813 *       the position of the insertion point when the text box
814 *       is clicked on. It computes the width of each character
815 *       in the string (as it would be rendered
816 *       on the screen) and determines which two characters the
817 *       pick position would fall between, in other words it finds
818 *       the nearest insertion point between characters.
819 *
820 *   INPUTS
821 *       font        The font to use in the character-width calculation.
822 *
823 *       str         The string to search.
824 *
825 *       pick        The pick position, relative to the start of the string.
826 *
827 *       maxLen      The length of the string.
828 *
829 *   RESULT
830 *       The index of the selected character.
831 *
832 **********************************************************************
833 */
WhichIChar(gFont * font,uint8 * s,int16 length,int16 maxLen)834 int16 WhichIChar(gFont *font, uint8 *s, int16 length, int16 maxLen) {
835 	int16           count = 0;
836 
837 	if (maxLen == -1)
838 		maxLen = strlen((char *)s);
839 
840 	for (count = 0; count < maxLen; count++) {
841 		uint8       chr = *s++;
842 		int16       width;
843 
844 		width = font->charKern[chr] + font->charSpace[chr];
845 
846 		if (length < width / 2)
847 			break;
848 		length -= width;
849 	}
850 	return count;
851 }
852 
853 /****** gtext.cpp/GTextWrap *****************************************
854 *
855 *   NAME
856 *       GTextWrap -- given text buffer and pixel width, find wrap point
857 *
858 *   SYNOPSIS
859 *       offset = GTextWrap( font, buffer, count, width, styles );
860 *
861 *       int32 GTextWrap( gFont *, char *, uint16 &, uint16, int16 );
862 *
863 *   FUNCTION
864 *       This function finds the point in a text buffer that text
865 *       wrapping would have to occur given the indicated pixel width.
866 *       Linefeeds are considered implied wrap points. Tabs are not
867 *       expanded.
868 *
869 *   INPUTS
870 *       font    a gFont that the text will be rendered in.
871 *       buffer  a NULL-terminated text buffer.
872 *       count   reference to variable to store count of characters
873 *               to render before wrap point.
874 *       width   width in pixels to wrap point.
875 *       styles  possible text styles.
876 *
877 *   RESULT
878 *       offset  amount to add to buffer for next call to GTextWrap,
879 *               or -1 if the NULL-terminator was reached.
880 *
881 **********************************************************************
882 */
GTextWrap(gFont * font,char * mark,uint16 & count,uint16 width,int16 styles)883 int32 GTextWrap(gFont *font, char *mark, uint16 &count, uint16 width, int16 styles) {
884 	char *text = mark;
885 	char *atext;
886 	uint16 pixlen;
887 	int aTextIndex = 0;
888 
889 	if (!strchr(text, '\n')) {
890 		count = strlen(text);
891 		pixlen = TextWidth(font, text, count, styles);
892 		if (pixlen <= width)
893 			return -1;
894 	}
895 
896 	atext = text;
897 	while (1) {
898 		Common::String s = atext;
899 
900 		int nTextIndex = aTextIndex + s.findFirstOf(' ');
901 		int lTextIndex = aTextIndex + s.findFirstOf('\n');
902 
903 		if (!s.contains(' ') || (s.contains('\n') && lTextIndex < nTextIndex)) {
904 			pixlen = TextWidth(font, text, lTextIndex, styles);
905 			if (pixlen <= width) {
906 				count = lTextIndex;
907 				return count + 1;
908 			}
909 
910 			if (!s.contains(' ')) {
911 				if (atext == text)
912 					break;
913 
914 				count = aTextIndex - 1;
915 				return count + 1;
916 			}
917 		}
918 
919 		pixlen = TextWidth(font, text, nTextIndex, styles);
920 		if (pixlen > width) {
921 			if (atext == text)
922 				break;
923 
924 			count = aTextIndex - 1;
925 			return count + 1;
926 		}
927 
928 		atext = text + nTextIndex + 1;
929 		aTextIndex = nTextIndex + 1;
930 	}
931 
932 	if (atext == text) {
933 		// current text has no spaces AND doesn't fit
934 
935 		count = strlen(text);
936 		while (--count) {
937 			pixlen = TextWidth(font, text, count, styles);
938 			if (pixlen <= width)
939 				return count;
940 		}
941 	} else {
942 		count = aTextIndex - 1;
943 		return count + 1;
944 	}
945 
946 	return -1;
947 }
948 
949 } // end of namespace Saga2
950